Skip to content

Instantly share code, notes, and snippets.

@sizovs
Last active December 5, 2024 16:49
Show Gist options
  • Save sizovs/b2b0b1dfed8671435798e410093e5393 to your computer and use it in GitHub Desktop.
Save sizovs/b2b0b1dfed8671435798e410093e5393 to your computer and use it in GitHub Desktop.
Solutions.java
// 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