-
-
Save sprour/8ec4b12f0a60b4e958d1e87f27539559 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; | |
IntStream | |
.rangeClosed(from, to) | |
.mapToObj(Port::new) | |
.filter(Port::isFree) | |
.findFirst(); | |
class Port { | |
// throws IOException on a network connection failure. | |
boolean isFree() throws IOException { | |
... | |
} | |
} | |
// 2 Refactor code to a higher (and single) level of abstraction | |
// 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() { | |
var credentials = validCredentials() | |
var user = newUser(credentials); | |
DomainEvents.publish(new RegistrationCompleted(user)); | |
} | |
private Credentials validCredentials(){ | |
if (!form.isValid()) { | |
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); | |
} | |
return new Credentials(username, password); | |
} | |
} | |
class Credentials { | |
public Username username; | |
public Password password; | |
public Credentials(Username username, Password password){ | |
this.username = username; | |
this.password = password; | |
} | |
} | |
// 3. eliminate getters and setters and turn Member into an object | |
member.offers().add(offer); | |
member.activeOffers().increase(); | |
// 4. Find missing domain objects and reify (thingify) them. | |
borrower.loans().apply(new Loan(amount, term)); | |
// 5. Find a missing domain objects and reify (thingify) it. | |
class MortgageRiskService { | |
Risk calculate(MortageApplication mortgageApplication) { | |
... | |
} | |
boolean isTolerable(Risk risk) { | |
... | |
} | |
boolean areEquivalent(Risk oneRisk, Risk otherRisk) { | |
... | |
} | |
} | |
// 6. Find a missing domain objects and reify (thingify) it. | |
class Bankruptcy { | |
Probability calculate(Business business){ ... } | |
boolean isHigh(Probability probability){...} | |
} | |
// 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> { | |
Collection<T> lines; | |
CsvFile(File file) { ... } | |
Collection<T> lines() { ... } | |
} | |
// 8. Replace a procedural design (and an agent noun Pinger) with an object | |
interface Pinger { | |
void ping(); | |
} | |
// 9. Replace a procedural design (and an agent noun MoneyFormatter) with an object | |
interface Money { | |
String format(); | |
} | |
// 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> { | |
byte[] marshall(T object) { | |
try(ByteArrayOutputStream outStream = new ByteArrayOutputStream()) { | |
JaxbMarshaller jaxbMarshaller = new JaxbMarshaller(object.getClass()); | |
jaxbMarshaller.marshallObject(object, outStream); | |
return outStream.toByteArray(); | |
} | |
} | |
} | |
// 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 Permissions permissions = Permissions.DEFAULT; | |
void grant(Permissions permissions) { | |
this.permissions = permissions | |
} | |
} | |
// 13. Because of CQS, a naming conflict might arise. Fix it! (no getters & setters allowed). | |
user.banDetails() // returns user's ban and the corresponding information (if any) | |
user.ban() // bans a user | |
// 14. Can you spot an object that pretends as a service? Fix it! | |
interface BlacklistRequest { | |
String getEmail(); | |
String getIpAddress(); | |
boolean isBlocked(); | |
} | |
// 15. Turn this procedure into an object "AuthenticationToken" | |
class AuthenticationToken { | |
static from(String username, String password) throws UserDoesNotExistException; | |
} | |
// 16. fix naming issues | |
interface Suite { | |
interface Test { | |
void print(); | |
boolean isSuccessful() | |
} | |
void run(); | |
Collection<Suite.Test> tests(); | |
} | |
suite.run() | |
for (Suite.Test test : suite.tests()) { | |
if (!test.isSuccessful() ) { | |
// pretty printing | |
test.print(); | |
} | |
} | |
// 17. Can you SLAP (Single Level of Abstraction Principle) it? | |
boolean destroyButtonAvailable = | |
widgets | |
.stream() | |
.filter(Widget::isButton) | |
.filter(Button::isForArmagedon) | |
.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() { | |
return firstName | |
+ " " | |
+ nickName.map(nickName -> "<"+nickName+"> ").orElse("") | |
+ lastName; | |
} | |
} | |
// 19. | |
// from variable names, omit words that are deductable from the context | |
void openBankAccount() { | |
var withdrawalLimits = WithdrawalLimits.defaults(env); | |
var holder = new AccountHolder(...); | |
var account = new BankAccount(holder, withdrawalLimits); | |
account.open(); | |
account.deposit(bonus()); | |
accountRepository.save(account); | |
} | |
// 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 String rawPassword; | |
private SecurePassword(String rawPassword) { | |
this.rawPassword = rawPassword; | |
} | |
static from(Vault vault) { | |
return new SecurePassword(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