Last active
January 8, 2021 09:29
-
-
Save sizovs/18dccb51ba675e7cc61d2717246399c1 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). | |
0. | |
Range range = new Range(8000, 8005); | |
var port = Port.freeWithin(range); | |
1. | |
int from = 8000; | |
int to = 9000; | |
IntStream | |
.rangeClosed(from, to) | |
.mapToObj(Port::new) | |
.filter(wrap().predicate(Port::isFree)) | |
.findFirst(); | |
class Port { | |
// throws IOException on a network connection failure. | |
boolean isFree() throws IOException { | |
... | |
} | |
} | |
2. | |
int from = 8000; | |
int to = 9000; | |
IntStream | |
.rangeClosed(from, to) | |
.mapToObj(Port::new) | |
.filter(Port::isFree) | |
.findFirst(); | |
class Port { | |
boolean isFree() { | |
... | |
} | |
} | |
// 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() { | |
throwIfMissingCredentials(form); | |
var username = new Username(form.username()); | |
throwIfTaken(username); | |
var password = new Password(form.password()); | |
throwIfWeak(password); | |
var user = saveUser(username, password); | |
publishRegistrationCompletedEvent(user); | |
} | |
} | |
// 3. eliminate getters and setters and turn Member into an object | |
member.make(offer); | |
member.numberOfActiveOffers(); | |
// member.setNumberOfActiveOffers(member.getNumberOfActiveOffers() + 1); | |
// 4. Find missing domain objects and reify (thingify) them. | |
class Borrower | |
Loans existingLoansByDate; | |
Loan applyForALoan(LoanApplication application) { | |
} | |
// 5. Find a missing domain objects and reify (thingify) it. | |
class Risk { | |
private final BigDecimal risk; | |
Risk(MortageApplication mortgageApplication) { | |
this.risk = calculate(...); | |
} | |
boolean isTolerable() { | |
... | |
} | |
boolean isEquivalentTo(Risk another) { | |
... | |
} | |
} | |
// 6. Find a missing domain objects and reify (thingify) it. | |
class BankruptcyProbability { | |
BankruptcyProbability(Business business) { ... } | |
boolean isHigh(RiskPolicy policy) { ... } | |
} | |
// 7. Replace a procedural design (and an agent noun CsvParser) with an object. Also, make sure the new design doesn't violate CQS (queries = nouns, commands = verbs). | |
class CsvFile<T extends Line> { | |
CsvFile(Path path) | |
Stream<T> lines() { | |
} | |
Streamable<T> lines() { // Protonpack | |
} | |
} | |
new CsvFile(path).lines().filter(...).forEach(...); | |
// 8. Replace a procedural design (and an agent noun Pinger) with an object | |
interface Ping { | |
void send(); | |
} | |
interface Host { | |
void ping(); | |
} | |
// 9. Replace a procedural design (and an agent noun MoneyFormatter) with an object | |
interface MoneyFormatter { | |
String format(Money money); | |
} | |
class IsoFormattedMoney { | |
IsoFormattedMoney(Money money) { | |
this.money = money; | |
} | |
@Override | |
String toString() { | |
.... | |
} | |
} | |
money.isoFormatted(); -> IsoFormattedMoney() | |
money.xxxFormatted(); -> XxxFormattedMoney() | |
System.out.println("You have: " + new IsoFormattedMoney(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 Xml { | |
Xml(Object entity) { ... } | |
byte[] bytes() { | |
try (var stream = new ByteArrayOutputStream()) { | |
var jaxb = new JaxbMarshaller(entity.getClass()); | |
jaxb.marshallObject(entity, stream); | |
return outStream.toByteArray(); | |
} | |
} | |
} | |
xml.asBytes(); | |
xml.bytes(); | |
// 11. /validate/ method is a query (returns the result), but it sounds like an action. Fix it! | |
interface Input { | |
boolean isValid(); | |
} | |
// 12. make Permissions "optional", ditch a setter and provide a domain-specific command. | |
class User { | |
private Optional<Permissions> permissions = Optional.empty(); | |
void grant(Permissions permissions) { | |
this.permissions = Optional.of(permissions); | |
} | |
void revokePermissions() { | |
this.permissions = Optional.empty(); | |
} | |
} | |
// 13. Because of CQS, a naming conflict might arise. Fix it! (no getters & setters allowed). | |
user.currentBan() // returns user's ban and the corresponding information (if any) | |
user.putBan() // bans a user | |
// 14. Can you spot an object that pretends as a service? Fix it! | |
interface Blacklist { | |
boolean contains(WebsiteVisitor visitor); | |
} | |
interface WebsiteVisitor { | |
String email(); | |
String ipAddress(); | |
} | |
// 15. Turn this procedure into an object "AuthenticationToken" | |
class AuthenticationToken { | |
AuthenticationToken(String username, String password) throws UserDoesNotExistException | |
asString() | |
} | |
// 16. fix 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) | |
// 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() { | |
var nick = nickName | |
.map(nick -> " <" + nick + "> ") | |
.orElse(" "); | |
return firstName + nick + lastName; | |
return nickName | |
.map(nick -> firstName + " <" + nick + "> " + lastName) | |
.orElseGet(() -> firstName + " " + lastName); | |
} | |
} | |
// 19. | |
// from variable names, omit words that are deductable from the context | |
void openNewBankAccount() { | |
var limits = WithdrawalLimits.defaults(env); | |
var accountHolder = new AccountHolder(...); | |
var account = new BankAccount(accountHolder, limits); | |
account.open(); | |
account.deposit(openingBonus()); | |
account.save(repository); | |
} | |
var password = new SecurePassword(vault); | |
throw RuntimeException(); | |
password.toString(); // 123 | |
password.toString(); // 123 | |
password.toString(); // 123 | |
password.toString(); // 123 | |
// 20. | |
// calling a logic (such as remote system call) in a constructor not always a good idea. Do you know how to fix that? | |
class SecurePassword { | |
private final Supplier<String> rawPassword; | |
private SecurePassword(Vault vault) { | |
this.rawPassword = memoized(vault::verySecurePassword); | |
} | |
@Override | |
public String toString() { | |
return rawPassword.get(); | |
} | |
} | |
class SecurePassword { | |
private final ConcurrentHashMap<Vault, String> cache = new ConcurrentHashMap<>(1, 1.0f); | |
private SecurePassword(Vault vault) { | |
this.vault = vault; | |
} | |
@Override | |
public String toString() { | |
return cache.computeIfAbsent(vault, vault::verySecurePassword); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment