Skip to content

Instantly share code, notes, and snippets.

@sizovs
Created September 24, 2021 07:27
Show Gist options
  • Save sizovs/342dde02f3e8d59ec2998cf4b36a88b9 to your computer and use it in GitHub Desktop.
Save sizovs/342dde02f3e8d59ec2998cf4b36a88b9 to your computer and use it in GitHub Desktop.
// 1. refactor the code so it's at the same level of abstraction (SLAP).
int from = 8000;
int to = 9000;
int availablePort = IntStream
.rangeClosed(from, to)
map(Port::new)
.filter(Port::isAvailable)
.findFirst();
class Port {
boolean isAvailable()
try {
return this.isAvailable();
} catch (IOException exception) {
throw new RuntimeException(exception);
}
}
}
// noexception
// faux-pas
int to = 9000;
int availablePort = IntStream
.rangeClosed(from, to)
map(Port::new)
.filter(wrap().get(Port::isAvailable))
.findFirst();
class Port {
boolean isAvailable() throws IOException
...
}
}
// 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() {
throwIfInvalidCredentials(form);
var username = new Username(form.username());
throwIfTaken(username);
var password = new Password(form.password());
throwIfWeak(password);
var user = new User(username, password);
user.save(repo);
announceUserRegistration(user);
}
}
// 3. eliminate getters and setters and turn Member data class into an object
member.assign(offer);
// 4. Find missing domain objects and reify (thingify) them.
borrower.applyForALoan(LoanApplication application, ExistingLoans existingLoans);
// 5. Find a missing domain object and reify (thingify) it.
class MortgageRisk {
private final BigDecimal risk;
MortageRisk(MortageApplication mortgageApplication) {
this.risk = calculateRisk(mortgageApplication);
}
private BigDecimal calculateRisk(MortageApplication mortgageApplication) {
...
}
boolean isTolerable() {
...
}
@Override
boolean equals(Object obj) {
if (obj instanceof MortgageRisk anotherRisk) {
return this.risk.equals(anotherRisk.risk);
}
return false;
}
}
// 6. Find a missing domain object and reify (thingify) it.
class BankruptcyProbability {
private final BigDecimal probability;
BankruptcyProbability(Business business) {
this.probability = calculate(business);
}
private BigDecimal calculate(Business business) { ... }
boolean boolean isHigh() { ... }
}
// Technically, Business can also calculate probability. The question โ€“ should it?
// The answer: depends on how you organize package/bounded context dependencies.
// 7. Find a missing domain object (hint: CsvF...) and eliminate an agent class CsvParser.
class CsvFile<T extends Line> {
CsvFile(Path location) {
this.location = location;
}
// ProtonPack
Streamable<T> lines() {
}
}
csvFile.parse().stream()...
csvFile.lines().filter...
// 8. Find a missing domain object and eliminate an agent class Pinger.
interface Host {
void ping();
}
interface Ping {
void send();
}
// 9. Find a missing domain object and eliminate an agent class MoneyFormatter.
interface FormattedMoney {
class Pretty {
PrettyMoney(Money money) {
}
@Override
String toString() {
....
}
}
class Iso1234 {
Iso1234Format(Money money) {
}
@Override
String toString() {
....
}
}
}
System.out.printf("Hello %s, you have %s in cash", username, new Pretty(money));
money.format()
money.formatPretty()
money.formatIso1234()
money.format(pattern)
// 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> {
private final T entity;
Xml(T entity) {
this.entity = entity;
}
byte[] bytes() { // asBytes() also pretty good
try (var xml = new ByteArrayOutputStream()) {
var jaxb = new JaxbMarshaller(entity.getClass());
jaxb.marshallObject(entity, xml);
return xml.toByteArray();
}
}
}
// 11. Fix a bad method naming.
interface Input {
boolean isValid();
}
// 12. make Permissions "optional", and replace a dumb setter with a domain-specific method.
class User {
private Optional<Permissions> permissions = Optional.empty();
void grant(Permissions permissions) {
this.permissions = Optional.of(permissions);
}
void revokePermissions() {
this.permissions = Optional.empty();
}
}
// 13. Can you resolve a naming conflict? no getters & setters allowed!
user.currentBan()
user.putABan()
// 14. Can you spot a domain object that pretends as a BlacklistingService and refactor the code accordingly?
interface Blacklist {
boolean contains(WebsiteVisitor visitor);
}
interface WebsiteVisitor {
String email();
String ipAddress();
}
// 15. Turn this agent noun into an object "AuthenticationToken"
interface AuthenticationToken {
AuthenticationToken(String username, String password) throws UserDoesNotExistException {
this.token = ....(username, password);
}
@Override
String toString() {
return this.token;
}
}
// 16. fix different naming issues
interface Suite {
interface Test {
void print();
boolean hasFailed()
}
void runAndWait();
Collection<Test> tests();
}
suite.runAndWait()
suite.tests().stream().filter(Test::hasFailed).forEach(Test::prettyPrint)
suite.tests().stream().filter(not(Test::isSuccessful).or(...).and(...)).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() {
return nickName
.map(nick -> firstName + "< " + nick + "> " + lastName)
.orElseGet(() -> firstName + " " + lastName);
}
String fullName() {
var nick = nickName.map(it -> "< " + it + "> ").orElse(" ")
return firstName + nick + lastName;
}
๐Ÿš€ String fullName() {
return firstName + nickName.isPresent() ? " <" + nickName.get() + "> " : " " + 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());
repo.save(account);
}
// 20. Nesting is pretty hard to reason about, as it doesn't convey the order of transformations.
// Can you refactor the code to eliminate nesting?
private static String normalize(String fullName) {
return removeAllEmojis((capitalizeFully(stripAccents(fullName))));
}
var password = new SecurePassword(vault);
---
throws for whatever reason
---
System.out.println("Your password is:" + password); // 12342342
System.out.println("Your password is:" + password); // 12342342
System.out.println("Your password is:" + password); // 12342342
System.out.println("Your password is:" + password); // 12342342
System.out.println("Your password is:" + password); // 12342342
// 21.
// calling a logic (such as remote system call) in a constructor is not always a good idea. Do you know how to fix that?
class SecurePassword {
private final ConcurrentHashMap<Vault, String> foreverCache = new ConcurrentHashMap<>(1, 1.0f);
private final Vault vault;
private SecurePassword(Vault vault) {
this.vault = vault;
}
@Override
public String toString() {
return foreverCache.computeIfAbsent(vault, vault::generateSecurePassword)
}
}
// 21.
// calling a logic (such as remote system call) in a constructor is 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 = memoize(vault::generateSecurePassword);
}
@Override
public String toString() {
return password.get();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment