It might seem obvious, but recently I saw a colleague use Optional in a weird way.
When I see a piece of code that seems to contain about 3 or more Optional.isPresent() if checks and Optional.get() method calls, I kind of panic.
To be absolutely fair, the code wasn't ready yet, so was going to change anyways.
So, anyways, before we had Optionals, we had to deal with NULL a lot.
The way to do this was as follows:
private boolean documentAccessAllowed() { Information information = session.getInformation(); if (information != null) { Document document = database.getDocument(information.getDocumentnumber()); if (document != null) { if (!document.getOwner().equals(user)) { Administrator administrator = database.getAdmininstrator(user.getName()); if (administrator == null) { addErrorMessage("You are not allowed access to this document."); return false; } } } } return true; }
I realize this code could be refactored using "Replace Nested Conditional with Guard Clauses", but some purists frown upon multiple return statements in a method. I don't mind. But let's continue with the example as is.
Then we got Optionals, yay! But, translating the above code verbatim causes my head to explode:
private boolean documentAccessAllowed() { Optional<Information> information = session.getInformation(); if (information.isPresent()) { Optional<Document> document = database.getDocument(information.get().getDocumentnumber()); if (document.isPresent()) { if (!document.get().getOwner().equals(user)) { Optional<Administrator> administrator = database.getAdmininstrator(user.getName()); if (administrator.isEmpty()) { addErrorMessage("You are not allowed access to this document."); return false; } } } } return true; }
This code is of course hardly ideal!
A better way is to use the flatMap and map and filter functions of Optional, which seems more concise, but requires a bit of a mental adjustment.
private boolean documentAccessAllowed() { Boolean allowed = session.getInformation() .map(Information::getDocumentnumber) .flatMap(database::getDocument) .filter(doc -> !doc.getOwner().equals(user)) .filter(doc -> database.getAdmininstrator(user.getName()).isEmpty()) .isEmpty(); if (!allowed) { addErrorMessage("You are not allowed access to this document."); } return allowed; }
The advice of a colleague of mine is that lambdas, even slightly trivial ones, are a bit hard to read, and making it a method with a good name helps clear things up immensely, like so:
private boolean documentAccessAllowed() { boolean allowed = session.getInformation() .map(Information::getDocumentnumber) .flatMap(database::getDocument) .filter(doc -> !isOwner(user, doc)) .filter(doc -> !isAdministrator(database, user)) .isEmpty(); if (!allowed) { addErrorMessage("You are not allowed access to this document."); } return allowed; }
The idea here is not to make the code short (although that most often helps) but make the code very easy to read and follow.
In the example above, there's a lot of use of negation, which I don't like either. (isEmpty(), !isAdmin, !isOwner) Sometimes shoehorning boolean expressions so you can put them in sequential filters is a bad idea. Perhaps the following is more clear:
private boolean documentAccessAllowed(Session session, Database database, User user) { boolean allowed = session.getInformation() .map(Information::getDocumentnumber) .flatMap(database::getDocument) .filter(doc -> isOwnerOrAdministrator(database, user, doc)) .isPresent(); if (!allowed) { addErrorMessage("You are not allowed access to this document."); } return allowed; } private static boolean isOwnerOrAdministrator(Database database, User user, Document doc) { return isOwner(user, doc) || isAdministrator(database, user); }
Let me know if you have any suggestions, of which there are no doubt several, on how this can be improved in readability.
No comments:
Post a Comment