Last active
November 12, 2023 14:11
-
-
Save sizovs/927a445203f885547976ef36fa0a978b to your computer and use it in GitHub Desktop.
This file contains 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) | |
.filter(num -> { | |
try { | |
var port = new Port(num); | |
return port.isAvailable(); | |
} catch (IOException exception) { | |
throw new RuntimeException(exception); | |
} | |
}) | |
.findFirst(); | |
class Port { | |
// throws IOException on a network connection failure. | |
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() { | |
if (isEmpty(form.username()) || isEmpty(form.password()) { | |
throw new RegistrationException(MISSING_CREDENTIALS); | |
} | |
var username = new Username(form.username()); | |
var isUsernameTaken = username.satisfies(new IsTaken(repo)); | |
if (isUsernameTaken) { | |
throw new RegistrationException(USERNAME_TAKEN); | |
} | |
var password = new Password(form.password()); | |
var isWeakPassword = password.satisfies(new IsWeak(repo)); | |
if (isWeakPassword) { | |
throw new RegistrationException(WEAK_PASSWORD); | |
} | |
var user = new User(username, password); | |
user.save(repo); | |
DomainEvents.publish(new RegistrationCompleted(user)); | |
} | |
} | |
// 3. eliminate getters and setters and turn Member data class into an object | |
member.getAssignedOffers().add(offer); | |
member.setNumberOfAssignedOffers(member.getNumberOfAssignedOffers() + 1); | |
// 4. Find missing domain objects and reify (thingify) them. | |
borrower.applyForALoan(BigDecimal amount, int term, Map<LocalDateTime, Id> existingLoansByDate); | |
// 5. Find a missing domain objects and reify (thingify) it. | |
class MortgageRiskService { | |
BigDecimal calculateRisk(MortageApplication mortgageApplication) { | |
... | |
} | |
boolean isTolerable(BigDecimal risk) { | |
... | |
} | |
boolean areRisksEquivalent(BigDecimal oneRisk, BigDecimal otherRisk) { | |
... | |
} | |
} | |
// 6. Find a missing domain objects and reify (thingify) it. | |
class BankruptcyProbabilityCalculator { | |
BigDecimal calculate(Business business) { ... } | |
} | |
class BankruptcyProbabilityUtils { | |
boolean isHigh(BigDecimal decimal) { ... } | |
} | |
// 7. Find a missing domain object (hint: CsvF...) and eliminate an agent class CsvParser. | |
class CsvParser<T extends Line> { | |
Collection<T> parse(File location) { | |
} | |
} | |
// 8. Find a missing domain object and eliminate an agent class Pinger. | |
interface Pinger { | |
void sendPing(); | |
} | |
// 9. Find a missing domain object and eliminate an agent class MoneyFormatter. | |
interface MoneyFormatter { | |
String format(Money money); | |
} | |
// 10. Turn XmlMarshaller into a class Xml | |
// + Make the class generic (decouple it from Invoice) | |
// + Use type inference | |
// + Use try-with-resources block | |
class XmlMarshaller { | |
byte[] marshallToXml(Invoice invoice) { | |
ByteArrayOutputStream outStream = new ByteArrayOutputStream(); | |
JaxbMarshaller jaxbMarshaller = new JaxbMarshaller(Invoice.class); | |
jaxbMarshaller.marshallObject(invoice, outStream); | |
byte[] resultXml = outStream.toByteArray(); | |
IOUtils.closeQuietly(outStream); | |
return resultXml; | |
} | |
} | |
// 11. Fix a bad method naming. | |
interface Input { | |
boolean validate(); | |
} | |
// 12. make Permissions "optional", and replace a dumb setter with a domain-specific method. | |
class User { | |
private Permissions permissions; | |
void setPermissions(Permissions permissions) { | |
this.permissions = permissions | |
} | |
} | |
// 13. Can you resolve a naming conflict? no getters & setters allowed! | |
user.ban() // returns user's ban and the corresponding information (if any) | |
user.ban() // bans a user | |
// 14. Can you spot a domain object that pretends as a BlacklistingService and refactor the code accordingly? | |
interface BlacklistingService { | |
boolean shouldBlockWebsiteVisitor(BlacklistRequest request); | |
} | |
interface BlacklistRequest { | |
String getEmail(); | |
String getIpAddress(); | |
} | |
// 15. Turn this agent noun into an object "AuthenticationToken" | |
interface AnonymousUserAuthenticator { | |
String authenticate(String username, String password) throws UserDoesNotExistException; // returns authentication token | |
} | |
// 16. fix different naming issues | |
interface Suite { | |
interface SuiteTest { | |
void print(); | |
boolean successfulTest() | |
} | |
void runAndWait(); | |
Collection<SuiteTest> listSuiteTests(); | |
} | |
suite.runAndWait() | |
for (SuiteTest suiteTest : suite.listSuiteTests()) { | |
if (!suiteTest.successfulTest() ) { | |
// pretty printing | |
suiteTest.print(); | |
} | |
} | |
// 17. Can you SLAP (Single Level of Abstraction Principle) it? | |
boolean destroyButtonAvailable = | |
widgets | |
.stream() | |
.filter(Widget::isButton) | |
.filter(button -> button.label().equals("Destroy The World")) | |
.findAny() | |
.isPresent(); | |
// 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() { | |
// ... | |
} | |
} | |
// 19. | |
// from variable names, omit words that are deductable from the context | |
void openNewBankAccount() { | |
var bankAccountWithdrawalLimits = WithdrawalLimits.defaults(env); | |
var bankAccountHolder = new AccountHolder(...); | |
var bankAccount = new BankAccount(bankAccountHolder, bankAccountWithdrawalLimits); | |
bankAccount.open(); | |
bankAccount.deposit(openingBonus()); | |
bankAccountRepository.save(bankAccount); | |
} | |
// 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)))); | |
} | |
// 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 String rawPassword; | |
private SecurePassword(Vault vault) { | |
this.rawPassword = vault.verySecurePassword(); | |
} | |
public String raw() { | |
return rawPassword; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment