Skip to content

Instantly share code, notes, and snippets.

@sizovs
Last active May 28, 2021 07:39
Show Gist options
  • Save sizovs/9d403cea48b3ac2990bfc2b04968852d to your computer and use it in GitHub Desktop.
Save sizovs/9d403cea48b3ac2990bfc2b04968852d to your computer and use it in GitHub Desktop.
Playtech May
// 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