Last active
May 28, 2021 07:39
-
-
Save sizovs/9d403cea48b3ac2990bfc2b04968852d to your computer and use it in GitHub Desktop.
Playtech May
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; | |
IntStream | |
.rangeClosed(from, to) | |
.mapToObj(Port::new) | |
.filter(throwingPredicate(Port::isFree)) // sneakily throws the original exception (faux-pas) | |
.filter(wrap().predicate(Port::isFree)) // wraps unchecked exception (noexceptions) | |
.findFirst(); | |
class Port { | |
boolean isFree() throws IOException { | |
... | |
} | |
} | |
int from = 8000; | |
int to = 9000; | |
IntStream | |
.rangeClosed(from, to) | |
.mapToObj(Port::new) | |
.filter(Port::isFree) | |
.findFirst(); | |
class Port { | |
boolean isFree() { | |
try { | |
return this.isFreeUnchecked(); | |
} catch (IOException exception) { | |
throw new RuntimeException(exception); | |
} | |
} | |
} | |
// 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(); | |
throwIfUsernameIsTaken(); | |
throwIfPasswordIsWeak(); | |
validateCredentials(); | |
validateUsername(); | |
validatePassword(); | |
var user = new User(form.username, form.password); | |
user.save(repo); | |
publish(new RegistrationCompleted(user)); | |
} | |
throwIfMissingCredentials() { | |
if (isEmpty(form.username()) || isEmpty(form.password()) { | |
throw new RegistrationException(MISSING_CREDENTIALS); | |
} | |
} | |
} | |
// 3. eliminate getters and setters and turn Member into an object | |
member.assign(offer); | |
// 4. Find missing domain objects and reify (thingify) them. | |
borrower.applyForALoan(LoanApplication application, Collection<Loan> existingLoans); | |
// 5. | |
class MortgageRisk { | |
MortgageRisk(MortageApplication mortgageApplication) { | |
this.risk = calculate(mortgageApplication); | |
} | |
boolean isTolerable() { | |
... | |
} | |
@Override | |
public equals(Object obj) { | |
if (obj instanceof MortageRisk other) { | |
return this.risk.equals(other.risk); | |
} | |
return false; | |
} | |
} | |
// 6. Find a missing domain objects and reify (thingify) it. | |
class BankruptcyProbability { | |
BankruptcyProbability(Business business) { | |
this.probability = calc(business); | |
} | |
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 CsvFile<T extends Line> { | |
CsvFile(Path location) { // java.nio vs. java.io | |
} | |
Collection<T> content(); // or lines(); | |
Stream<T> content(); // or lines(); | |
Streamable<T> content(); // Protonpack | |
} | |
var csvFile = new CsvFile(path); | |
csvFile.lines().map(...).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 PrettyMoney { | |
PrettyMoney(Money money) { | |
} | |
@Override | |
public toString() { | |
// format goes here | |
} | |
} | |
System.out.println("The amount is: " + new PrettyMoney(new Money(xxx)); | |
// 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) { | |
this.entity = entity; | |
} | |
byte[] bytes() { | |
try (var outputStream = new ByteArrayOutputStream()) { | |
var jaxb = new JaxbMarshaller(entity.getClass()); | |
jaxb.marshallObject(entity, outputStream); | |
return outputStream.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.NONE; | |
void grant(Permissions permissions) { | |
this.permissions = permissions | |
} | |
void revoke(Permissions permissions) { | |
this.permissions = NONE; | |
} | |
} | |
class User { | |
private Optional<Permissions> permissions = Optional.empty(); | |
void grant(Permissions permissions) { | |
this.permissions = Optional.of(permissions); | |
} | |
void revoke(Permissions permissions) { | |
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.putABan() // 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" | |
interface AuthenticationToken { | |
AuthenticationToken(String username, String password) throws UserDoesNotExistException; | |
asString(); | |
toString(); | |
} | |
// 16. fix naming issues | |
interface Suite { | |
interface Test { | |
void print(); | |
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(Widget::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() { | |
💩 const nick = nickName | |
.flatMap(nick -> " <" + nick + "> ") | |
.orElse(" "); | |
return firstName + nick + lastName; | |
💓 return nickName | |
.flatMap(nick -> firstName + " <" + nick + "> " + lastName) | |
.orElseGet(() -> firstName + " " + 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()); | |
repository.save(account); | |
} | |
// 20. Nesting is pretty hard to reason about, as it doesn't convey the order of transformations. | |
// Can you refactor to eliminate nesting? | |
private static String normalize(String fullName) { | |
return Stream.of(fullName) | |
.map(this::stripAccents) | |
.map(this::capitalizeFully) | |
.map(this:removeAllEmojis) | |
.map(this:y) | |
.map(this:x); | |
.getFirst(); | |
return x(y(removeAllEmojis((capitalizeFully(stripAccents(fullName)))))); | |
} | |
var password = new SecurePassword(vault); | |
System.out.println(password); // q42342 | |
System.out.println(password); // q42342 | |
System.out.println(password); // q42342 | |
System.out.println(password); // q42342 | |
// 21. | |
// 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 ConcurrentHashMap<Vault, String> cache = new ConcurrentHashMap<>(1, 1.0f); | |
private SecurePassword(Vault vault) { | |
this.vault = vault; | |
} | |
public String toString() { | |
return cache.computeIfAbsent(vault, vault::verySecurePassword); | |
} | |
} | |
// 21. | |
// 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> password; | |
private SecurePassword(Vault vault) { | |
this.password = Suppliers.memoize(vault::verySecurePassword); | |
} | |
public String toString() { | |
return password.get(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment