Created
September 24, 2021 07:27
-
-
Save sizovs/342dde02f3e8d59ec2998cf4b36a88b9 to your computer and use it in GitHub Desktop.
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. refactor the code so it's at the same level of abstraction (SLAP). | |
int from = 8000; | |
int to = 9000; | |
int availablePort = IntStream | |
.rangeClosed(from, to) | |
map(Port::new) | |
.filter(Port::isAvailable) | |
.findFirst(); | |
class Port { | |
boolean isAvailable() | |
try { | |
return this.isAvailable(); | |
} catch (IOException exception) { | |
throw new RuntimeException(exception); | |
} | |
} | |
} | |
// noexception | |
// faux-pas | |
int to = 9000; | |
int availablePort = IntStream | |
.rangeClosed(from, to) | |
map(Port::new) | |
.filter(wrap().get(Port::isAvailable)) | |
.findFirst(); | |
class Port { | |
boolean isAvailable() throws IOException | |
... | |
} | |
} | |
// 2 Refactor code to a higher (and single) level of abstraction | |
class Registration { | |
private final RegistrationForm form; | |
private final Repository repo; | |
Registration(RegistrationForm form, Repository repo) { | |
this.form = form; | |
this.repo = repo; | |
} | |
void complete() { | |
throwIfInvalidCredentials(form); | |
var username = new Username(form.username()); | |
throwIfTaken(username); | |
var password = new Password(form.password()); | |
throwIfWeak(password); | |
var user = new User(username, password); | |
user.save(repo); | |
announceUserRegistration(user); | |
} | |
} | |
// 3. eliminate getters and setters and turn Member data class into an object | |
member.assign(offer); | |
// 4. Find missing domain objects and reify (thingify) them. | |
borrower.applyForALoan(LoanApplication application, ExistingLoans existingLoans); | |
// 5. Find a missing domain object and reify (thingify) it. | |
class MortgageRisk { | |
private final BigDecimal risk; | |
MortageRisk(MortageApplication mortgageApplication) { | |
this.risk = calculateRisk(mortgageApplication); | |
} | |
private BigDecimal calculateRisk(MortageApplication mortgageApplication) { | |
... | |
} | |
boolean isTolerable() { | |
... | |
} | |
@Override | |
boolean equals(Object obj) { | |
if (obj instanceof MortgageRisk anotherRisk) { | |
return this.risk.equals(anotherRisk.risk); | |
} | |
return false; | |
} | |
} | |
// 6. Find a missing domain object and reify (thingify) it. | |
class BankruptcyProbability { | |
private final BigDecimal probability; | |
BankruptcyProbability(Business business) { | |
this.probability = calculate(business); | |
} | |
private BigDecimal calculate(Business business) { ... } | |
boolean boolean isHigh() { ... } | |
} | |
// Technically, Business can also calculate probability. The question โ should it? | |
// The answer: depends on how you organize package/bounded context dependencies. | |
// 7. Find a missing domain object (hint: CsvF...) and eliminate an agent class CsvParser. | |
class CsvFile<T extends Line> { | |
CsvFile(Path location) { | |
this.location = location; | |
} | |
// ProtonPack | |
Streamable<T> lines() { | |
} | |
} | |
csvFile.parse().stream()... | |
csvFile.lines().filter... | |
// 8. Find a missing domain object and eliminate an agent class Pinger. | |
interface Host { | |
void ping(); | |
} | |
interface Ping { | |
void send(); | |
} | |
// 9. Find a missing domain object and eliminate an agent class MoneyFormatter. | |
interface FormattedMoney { | |
class Pretty { | |
PrettyMoney(Money money) { | |
} | |
@Override | |
String toString() { | |
.... | |
} | |
} | |
class Iso1234 { | |
Iso1234Format(Money money) { | |
} | |
@Override | |
String toString() { | |
.... | |
} | |
} | |
} | |
System.out.printf("Hello %s, you have %s in cash", username, new Pretty(money)); | |
money.format() | |
money.formatPretty() | |
money.formatIso1234() | |
money.format(pattern) | |
// 10. Turn XmlMarshaller into a class Xml | |
// + Make the class generic (decouple it from Invoice) | |
// + Use type inference | |
// + Use try-with-resources block | |
class Xml<T> { | |
private final T entity; | |
Xml(T entity) { | |
this.entity = entity; | |
} | |
byte[] bytes() { // asBytes() also pretty good | |
try (var xml = new ByteArrayOutputStream()) { | |
var jaxb = new JaxbMarshaller(entity.getClass()); | |
jaxb.marshallObject(entity, xml); | |
return xml.toByteArray(); | |
} | |
} | |
} | |
// 11. Fix a bad method naming. | |
interface Input { | |
boolean isValid(); | |
} | |
// 12. make Permissions "optional", 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(); | |
} | |
} | |
// 13. Can you resolve a naming conflict? no getters & setters allowed! | |
user.currentBan() | |
user.putABan() | |
// 14. Can you spot a domain object that pretends as a BlacklistingService and refactor the code accordingly? | |
interface Blacklist { | |
boolean contains(WebsiteVisitor visitor); | |
} | |
interface WebsiteVisitor { | |
String email(); | |
String ipAddress(); | |
} | |
// 15. Turn this agent noun into an object "AuthenticationToken" | |
interface AuthenticationToken { | |
AuthenticationToken(String username, String password) throws UserDoesNotExistException { | |
this.token = ....(username, password); | |
} | |
@Override | |
String toString() { | |
return this.token; | |
} | |
} | |
// 16. fix different naming issues | |
interface Suite { | |
interface Test { | |
void print(); | |
boolean hasFailed() | |
} | |
void runAndWait(); | |
Collection<Test> tests(); | |
} | |
suite.runAndWait() | |
suite.tests().stream().filter(Test::hasFailed).forEach(Test::prettyPrint) | |
suite.tests().stream().filter(not(Test::isSuccessful).or(...).and(...)).forEach(Test::prettyPrint) | |
// 17. Can you SLAP (Single Level of Abstraction Principle) it? | |
boolean destroyButtonAvailable = | |
widgets | |
.stream() | |
.filter(Widget::isButton) | |
.map(Button::label) | |
.anyMatch("Destroy The World"::equals) | |
// 18. | |
// 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); | |
} | |
String fullName() { | |
var nick = nickName.map(it -> "< " + it + "> ").orElse(" ") | |
return firstName + nick + lastName; | |
} | |
๐ String fullName() { | |
return firstName + nickName.isPresent() ? " <" + nickName.get() + "> " : " " + lastName; | |
} | |
} | |
// 19. | |
// from variable names, omit words that are deductable from the context | |
void openNewBankAccount() { | |
var withdrawalLimits = WithdrawalLimits.defaults(env); | |
var accountHolder = new AccountHolder(...); | |
var account = new BankAccount(accountHolder, withdrawalLimits); | |
account.open(); | |
account.deposit(openingBonus()); | |
repo.save(account); | |
} | |
// 20. Nesting is pretty hard to reason about, as it doesn't convey the order of transformations. | |
// Can you refactor the code to eliminate nesting? | |
private static String normalize(String fullName) { | |
return removeAllEmojis((capitalizeFully(stripAccents(fullName)))); | |
} | |
var password = new SecurePassword(vault); | |
--- | |
throws for whatever reason | |
--- | |
System.out.println("Your password is:" + password); // 12342342 | |
System.out.println("Your password is:" + password); // 12342342 | |
System.out.println("Your password is:" + password); // 12342342 | |
System.out.println("Your password is:" + password); // 12342342 | |
System.out.println("Your password is:" + password); // 12342342 | |
// 21. | |
// calling a logic (such as remote system call) in a constructor is not always a good idea. Do you know how to fix that? | |
class SecurePassword { | |
private final ConcurrentHashMap<Vault, String> foreverCache = new ConcurrentHashMap<>(1, 1.0f); | |
private final Vault vault; | |
private SecurePassword(Vault vault) { | |
this.vault = vault; | |
} | |
@Override | |
public String toString() { | |
return foreverCache.computeIfAbsent(vault, vault::generateSecurePassword) | |
} | |
} | |
// 21. | |
// calling a logic (such as remote system call) in a constructor is not always a good idea. Do you know how to fix that? | |
class SecurePassword { | |
private final Supplier<String> password; | |
private SecurePassword(Vault vault) { | |
this.password = memoize(vault::generateSecurePassword); | |
} | |
@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