Last active
December 5, 2024 16:49
-
-
Save sizovs/b2b0b1dfed8671435798e410093e5393 to your computer and use it in GitHub Desktop.
Solutions.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 1. Fix bad naming. | |
class ShoppingCart { | |
UUID id(); | |
boolean isEligibleForFreeShipping(); | |
remove(OrderItem item); | |
add(OrderItem item); | |
Collection<OrderItem> items(); | |
} | |
// 2. Eliminate noise. | |
void openBankAccount() { | |
var accountHolder = new BankAccountHolder(...); | |
var account = new BankAccount(accountHolder, WithdrawalLimit.defaults()); | |
account.open(); | |
account.deposit(signupBonus()); | |
accounts.add(account); | |
} | |
// 3. Inline nesting is hard to reason about, as it doesn't convey the transformation order. Please refactor. | |
// Option 1 (multiline + indentation = best) | |
String normalize(String fullName) { | |
return stripEmojis( | |
stripAccents( | |
capitalize(fullName))); | |
} | |
// Option 2 (Options + chaining) | |
String normalize(String fullName) { | |
return Optional.of(fullName) | |
.map(this::capitalize) | |
.map(this::stripAccents) | |
.map(this::stripEmojis) | |
.get(); | |
} | |
// Option 3 (Function chaining) | |
String normalize(String fullName) { | |
return chain(fullName, | |
this::capitalize, | |
this::stripAccents | |
this::stripEmojis); | |
@SafeVarargs | |
private static <T> T chain(T value, Function<T, T>... functions) { | |
return Arrays.stream(functions).reduce(Function.identity(), Function::andThen).apply(value); | |
} | |
// 4. Refactor Member data class into an object | |
member.assign(offer); | |
// 5. Find a missing domain object, and eliminate a meaningless domain object (service) with it. | |
class Risk { | |
private final BigDecimal risk; | |
Risk(MortageApplication mortgageApplication) { | |
this.risk = riskOf(mortgageApplication); | |
} | |
boolean isTolerable() { | |
... | |
} | |
boolean isEquivalentTo(Risk other) { | |
return this.risk.equals(other.risk); | |
} | |
} | |
// 6. Find a missing domain object, and meaningless domain object(s) with it. | |
class BankruptcyProbability { | |
BankruptcyProbability(Business business) { ... } | |
boolean isHigh() { ... } | |
} | |
// 7. Find a missing domain object, and eliminate CsvParser. | |
class CsvFile<T extends Line> { | |
CsvFile(Path path) | |
Stream<T> lines() { ... } | |
} | |
new CsvFile(path).lines().filter(...).forEach(...); | |
// 8. Find a missing domain object, and eliminate Pinger. | |
interface Ping { | |
void send(); | |
} | |
interface Host { | |
void ping(); | |
} | |
// 9. Find a missing domain object, and eliminate MoneyFormatter. Limitation: you can't change Money class. | |
class FormattedMoney { | |
FormattedMoney(Money money) { | |
this.money = money; | |
} | |
@Override | |
String toString() { ... } | |
} | |
// money.formatted(); -> FormattedMoney | |
// money.iso123Formatted(); -> Iso123Money | |
// money.prettyFormatted(); -> PrettyMoney | |
println("The amount is: " + new Pretty(money)); | |
// 10. Turn XmlMarshaller into a class Xml | |
// + Make the class generic (decouple it from Invoice) | |
// + Use try-with-resources block | |
// + Minimize noise | |
class Xml { | |
Xml(Object entity) { | |
this.entity = entity; | |
} | |
byte[] bytes() { | |
try (var output = new ByteArrayOutputStream()) { | |
var jaxb = new JaxbMarshaller(entity.getClass()); | |
jaxb.marshallObject(entity, output); | |
return output.toByteArray(); | |
} | |
} | |
} | |
// 11. make class null-safe with Optionals, and replace a dumb setter with a domain-specific method. | |
class User { | |
private Optional<Permissions> permissions = Optional.empty(); | |
void grant(Permissions permissions) { | |
this.permissions = Optional.of(permissions); | |
} | |
void revokePermissions() { | |
this.permissions = Optional.empty(); | |
} | |
} | |
// 12. Can you resolve a naming conflict? no getters & setters allowed! | |
user.currentBan() // returns user's ban and the corresponding information (if any) | |
user.putABan() // bans a user | |
// 13. Can you spot a domain object that hides behind a BlacklistingService and refactor the code accordingly? | |
interface Blacklist { | |
boolean contains(WebsiteVisitor visitor); | |
} | |
record WebsiteVisitor(String email, String ipAddress) { } | |
// 14. Introduce meaningful objects, and replace exception throwing with Monads. | |
interface AuthenticationAttempt { | |
Maybe<AuthenticationToken, AuthenticationFailed> attempt() | |
} | |
// 15. fix different naming issues | |
interface Suite { | |
interface Test { | |
void prettyPrint(); | |
boolean hasFailed() | |
} | |
void runAndWait(); | |
Stream<Test> tests(); | |
} | |
suite.runAndWait() | |
suite.tests().filter(Test::hasFailed).forEach(Test::prettyPrint) | |
// 16. Can you stop mixing method refences with lambdas and SLAP (Single Level of Abstraction Principle) the code? | |
boolean destroyButtonAvailable = | |
widgets | |
.stream() | |
.filter(Widget::isButton) | |
.map(Button::label) | |
.anyMatch("Destroy The World"::equals); | |
// 17. | |
// implement the /fullName/ method so that it: | |
// 1. returned "firstName lastName" if nickname is missing | |
// 2. returned "firstName <nickname> lastName" if nickname is present. | |
// For example: "Robert Martin" or "Robert <Uncle Bob> Martin" | |
class User { | |
private final Optional<String> nickName; | |
private final String firstName; | |
private final String lastName; | |
String fullName() { | |
return nickName | |
.map(nick -> firstName + " <" + nick + "> " + lastName) | |
.orElseGet(() -> firstName + " " + lastName); | |
} | |
} | |
// 18. Checked exceptions don't work well with lambdas, and the Java community has largely moved away from using them. | |
// Unfortunately, sometimes checked exception come with legacy code we cannot change. | |
// Do you know how to fix this w/o modifying the Port class? | |
int availablePort = IntStream | |
.rangeClosed(8000, 9000) | |
.mapToObj(Port::new) | |
.filter(throwingPredicate(Port::isFree)) // sneakily throws the original exception (github.com/zalando/faux-pas) | |
.filter(wrap().predicate(Port::isFree)) // or wraps unchecked exception (github.com/robertvazan/noexception) | |
.findFirst(); | |
// 19. Can you make SecurePassword fetch the remote Vault lazily when toString() is called, | |
// ensuring the same password is returned on subsequent calls to toString()? | |
// Option 1 | |
class SecurePassword { | |
private final ConcurrentHashMap<Vault, String> password = new ConcurrentHashMap<>(1, 1.0f); | |
private SecurePassword(Vault vault) { | |
this.vault = vault; | |
} | |
@Override | |
public String toString() { | |
return password.computeIfAbsent(vault, vault::verySecurePassword); | |
} | |
} | |
// Option 2 (Using Guava) | |
class SecurePassword { | |
private final Supplier<String> password; | |
private SecurePassword(Vault vault) { | |
this.password = Suppliers.memoized(vault::verySecurePassword); | |
} | |
@Override | |
public String toString() { | |
return password.get(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment