-
-
Save deman777/19273f89c66cd822cb295dfdcf6290cf 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 { | |
boolean isFree() { | |
try { | |
throw new IOException(); | |
} catch (IOException e) { | |
throw new RuntimeException(e); | |
} | |
} | |
} | |
// 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() { | |
validate(form); | |
var username = new Username(form.username()); | |
validate(username); | |
var password = new Password(form.password()); | |
validate(password); | |
createUser(username, password); | |
} | |
private void createUser(Username username, Password password) { | |
var user = new User(username, password); | |
user.save(repo); | |
DomainEvents.publish(new RegistrationCompleted(user)); | |
} | |
private void validate(Password password) { | |
var isWeakPassword = password.satisfies(new IsWeak(repo)); | |
if (isWeakPassword) { | |
throw new RegistrationException(WEAK_PASSWORD); | |
} | |
} | |
private void validate(Username username) { | |
if (username.satisfies(new IsTaken(repo))) { | |
throw new RegistrationException(USERNAME_TAKEN); | |
} | |
} | |
private void validate(RegistrationForm form) { | |
if (isEmpty(form.username()) || isEmpty(form.password()){ | |
throw new RegistrationException(MISSING_CREDENTIALS); | |
} | |
} | |
} | |
// 3. eliminate getters and setters and turn Member into an object | |
member.add(offer); | |
// 4. Find missing domain objects and reify (thingify) them. | |
class LoanApplication { | |
public final BigDecimal amount; | |
public final int term; | |
} | |
class Borrower() { | |
private final Map<LocalDateTime, Id> loans; | |
public apply(LoanApplication application) { | |
... | |
} | |
} | |
// 5. Find a missing domain objects and reify (thingify) it. | |
class MortageApplication { | |
} | |
class Risk { | |
public Risk(MortageApplication application) { | |
... | |
} | |
boolean isTolerable() { | |
... | |
} | |
boolean isEquivalent(Risk otherRisk) { | |
... | |
} | |
} | |
// 6. Find a missing domain objects and reify (thingify) it. | |
class BankruptcyProbability { | |
private final decimal value; | |
BankruptcyProbability(Business business) { | |
this.value = ... | |
} | |
boolean isHigh() | |
} | |
// 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 Csv<T extends Line> { | |
Csv(File location) { | |
... | |
} | |
public Collection<T> get(); | |
} | |
// 8. Replace a procedural design (and an agent noun Pinger) with an object | |
interface Ping { | |
void send(); | |
} | |
// 9. Replace a procedural design (and an agent noun MoneyFormatter) with an object | |
interface Money { | |
String format(); | |
} | |
class Xml<T> { | |
private final T value; | |
private byte[] bytes; | |
private String string; | |
public Xml(T value) { | |
this.value = value; | |
} | |
public byte[] bytes() { | |
if (bytes == null) { | |
try(var outStream = new ByteArrayOutputStream()) { | |
new JaxbMarshaller(value.getClass()).marshallObject(value, outStream); | |
bytes = outStream.toByteArray(); | |
} | |
} | |
return bytes; | |
} | |
public String string() { | |
if (string == null) { | |
... | |
} | |
return string; | |
} | |
} | |
// 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 with(Permissions permissions) { | |
this.permissions = Optional.of(permissions); | |
} | |
} | |
// 13. Because of CQS, a naming conflict might arise. Fix it! (no getters & setters allowed). | |
user.banState() // 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 Blacklist { | |
boolean add(BlacklistRequest request); | |
} | |
interface BlacklistRequest { | |
String email(); | |
String ipAddress(); | |
} | |
// 15. Turn this procedure into an object "AuthenticationToken" | |
interface AuthenticationToken { | |
String AuthenticationToken(String username, String password) throws UserDoesNotExistException; // returns authentication token | |
} | |
// 16. fix naming issues | |
interface Suite { | |
interface Test { | |
void print(); | |
boolean successful() | |
} | |
void runAndWait(); | |
Collection<Test> tests(); | |
} | |
suite.runAndWait() | |
for (Test test : suite.tests()) { | |
if (!test.successful() ) { | |
// pretty printing | |
test.print(); | |
} | |
} | |
// 17. Can you SLAP (Single Level of Abstraction Principle) it? | |
boolean destroyButtonAvailable = | |
widgets | |
.stream() | |
.filter(Widget::isButton) | |
.filter(byLabel("Destroy The World")) | |
.findAny() | |
.isPresent(); | |
Predicate<Widget> byLabel(String label) { | |
button -> button.label().equals("Destroy The World") | |
} | |
// 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() { | |
Stream.of( | |
Stream.of(firstName), | |
nickName.stream(), | |
Stream.of(lastName) | |
) | |
.flatMap(identity()) | |
.collect(joining(" ")); | |
} | |
} | |
// 19. | |
// from variable names, omit words that are deductable from the context | |
void openNewBankAccount() { | |
var limits = WithdrawalLimits.defaults(env); | |
var holder = new AccountHolder(...); | |
var account = new BankAccount(holder, limits); | |
account.open(); | |
account.deposit(openingBonus()); | |
repository.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 raw; | |
private SecurePassword(String raw) { | |
this.raw = raw; | |
} | |
public String raw() { | |
return raw; | |
} | |
public static SecurePassword from(Vault vault) { | |
return new SecurePassword(vault.verySecurePassword()); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment