Skip to content

Instantly share code, notes, and snippets.

@sizovs
Last active November 26, 2020 15:31
Show Gist options
  • Save sizovs/2088184aa06fbc54d28bd06ad768c278 to your computer and use it in GitHub Desktop.
Save sizovs/2088184aa06fbc54d28bd06ad768c278 to your computer and use it in GitHub Desktop.

Link to this Gist

Training

Books

  • Design Patterns (Head First Design Patterns is easier to grasp than classical GoF Pattern book)
  • Head First Object Oriented Analysis & Design (McLaughlin)
  • 99 Bottles of OOP (echo chambers, name and type decoupling: saku vs. beer vs. beverage)
  • Domain-Driven Design (Evans)
  • Clean Code (Martin)
  • Clean Coder (Martin)
  • The Software Craftsman (Mancuso)
  • Elegant Objects (Vol 1) (Bugayenko)
  • Refactoring (Fowler)
  • Growing Object-Oriented Software Guided by Tests (Freeman, Pryce)
  • Secure by Design (implementing objects that protect their invariants)
  • Thinking, Fast and Slow (to understand System I and System II)
  • Team Topologies (to understand the concept of Cognitive Load)

People

  • Kent Beck (the creator of eXtreme Programming and TDD, coined the term "reveal intent, hide implementation")

Package dependencies and modularization

Databases / persistence:

The is no better way to learn how things go wrong, than writing a bunch of nice unit tests that reveal concurrency and locking issues.

Coding

SOLID

  • SRP (Single Responsibility Principle)
  • OCP (Open Closed Principle)
  • LSP (Liskov Substitution Principle)
  • ISP (Interface Segregation Principle)
  • DIP (Dependency Inversion Princiciple)

🍿 https://vimeo.com/60193259

Possible exercise solutions

// 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(wrap().predicate(Port::free)) // or sneak() or just create isFree() in "Port" class
        .findFirst();

// noexceptions (machinezoo)
// faux-pas




// 2. Find a missing domain object and reify (thingify) it.
class Company {
    private VatNumber vatNumber;

Vat -> Value Added Tax
VatNumber -> Value Added Tax Number


        
// 3. Find missing domain objects and reify (thingify) them.
borrower.applyForALoan(Application application, Loans loans);


class Application {
   BigDecimal amount, int term
}

class Loans {
  Map<LocalDateTime, Id> loansByDate;
}
        

// class' dependency direction depends on how you design package dependencies. If you don't
// care about package deps, then both solutions are OK (mortgage app -> risk AND risk -> mortgage app).

ee.playtech.mortgage.domain.application
- MortgageApplication

ee.playtech.mortage.domain.risk
- Risk

// application coupled to risk
application.risk()
    
// risk coupled to application
class Risk {
  private final BigDecimal risk;
  
  Risk(MortageApplication application) {
    this.risk = calculateSomehowUsing(application);
  }
  
  boolean isTolerable() {
    ...
  }  
  
  boolean equals(@Nullable Object that) {
     if (that instanceof Risk) {
       Risk otherRisk = (Risk) that;
       return this.risk.equalsTo(otherRisk.risk);
     }
     return false;
    ...
  }  
}
        

// 5. Find a missing domain objects and reify (thingify) it.
class BankruptcyProbabilityCalculator {
    BigDecimal calculate(Business business) { ... }
}
        
class BankruptcyProbabilityUtils {
  boolean isHigh(BigDecimal decimal) { ... }
}        

class BankruptcyProbability {
  BankruptcyProbability(Business business) {
    ... 
  }
  
   boolean isHigh() {
   }
}

BankruptcyProbability bankruptcyProbability = new BankruptcyProbability(business, repository);
bankruptcyProbability.isHigh()



// 6. 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 CsvParser<T extends Line> { 
    Collection<T> parse(File location) { 
    } 
} 

class CsvFile<T extends Line> { 
    CsvFile(Path path) {
      this.path = path;
    }
    Stream<T> lines() { 
      // Collection.add() // sort() // remove() // contains() // stream()
    } 
} 
  
new CsvFile(path).lines().filter(...).forEach(System.out::println);  


        
// 7. Replace a procedural design (and an agent noun Pinger) with an object
interface Ping { 
    void send(); 
}

interface Host {
    void ping();
}


        

// 8. Replace a procedural design (and an agent noun MoneyFormatter) with an object
interface MoneyFormatter {
  String format(Money money);
}

String formattedMoney = money.formatted(); 

class Pretty {
  Pretty(Money) {
  }
  
  @Override
  public String toString() {
    // prettify money somehow
  }
}


System.out.println("Here is the amount of money user has: " + new Pretty(money));
        

// 9. 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> {
    Xml(T entity) {
        this.entity = entity;
    }
    byte[] bytes() {  // content()
        try(var result = new ByteArrayOutputStream()) {
          var jaxb = new JaxbMarshaller(entity.getClass()); 
          jaxb.marshallObject(entity, result); 
          return result.toByteArray(); 
        }
    } 
}
        

// 10. /validate/ method is a query (returns the result), but it sounds like an action. Fix it!
interface Input {
  boolean isValid();
}

if (input.isValid()) {
}


        
// 11. ditch setters and provide domain-specific commands.
class BankAccount {
    
    enum Status {
        CLOSED, OPEN
    }
    
    private Status status;
    
    void setStatus(Status status) {
        this.status = status;
    }
    
    void close() {
      // throw exception if someone tries to close the account with positive balance
      this.status = Status.CLOSED;
    }
    void open() {
      throw exception if bank is already open
      this.status = Status.OPEN;
    }
}        

// 12. make Permissions "optional", ditch a setter and provide a domain-specific command.
class User {
    
    private Optional<Permissions> permissions = Optional.empty();
    
    void grant(Permissions permissions) {
        this.permissions = Optinal.of(permissions);
    }
    
    void revokePermissions() {
      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!
@NonDisriminative
interface Blacklist { 
    boolean contains(WebsiteVisitor visitor); 
} 

interface WebsiteVisitor { 
     String email(); 
     String ipAddress(); 
} 

if (blacklist.contains(websiteVisitor)) {
 ...
}

        
        
        
        
// 15. Turn this procedure into an object "AuthenticationToken"
interface AnonymousUserAuthenticator {
    String authenticate(Credentials credentials) throws UserDoesNotExistException; // returns authentication token
}

class AuthenticationToken {
  AuthenticationToken(Credentials credentials) { throws UserDoesNotExistException
  }
  
  @Override
  public String toString() {
    ...
  }
}

        
        
// 16. fix naming issues
interface Suite { 
    interface Test { 
        void print();
        boolean hasFailed()
    } 
    void runAndWait(); 
    Collection<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(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;
    
   1. String fullName() {
     var nick = nickname.map(nick -> " <" + nick + "> " ).orElse(" ");
     return firstName + nick + lastName;
     
    // ...
   }    
    
   2. String fullName() {
     return nickName
        .map(nick -> firstName + " <" + nick + "> " + lastName)
//        .orElse(firstName + " " + lastName);
        .orElseGet(() -> firstName + " " + lastName);        
     
    // ...
   }
}

      
var securePassword = new SecurePassword(vault); 





securePassword.raw(); // 1234 // remote
securePassword.raw(); // 1234 // local
securePassword.raw(); // 1234 // local
securePassword.raw(); // 1234 // local
securePassword.raw(); // 1234 // local
      

// 19.
// 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 Vault vault;
    
    private SecurePassword(Vault vault) {
        this.vault = vault.verySecurePassword();
    }
    
    synchronized public String raw() {
        if (rawPassword == null) {
          rawPassword = vault.verySecurePassword();;
        }
        return rawPassword;
    }    
    
}      

// 19.
// 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 Vault vault;
    
    private SecurePassword(Vault vault) {
        this.vault = vault.verySecurePassword();
    }
    
      public String raw() {
        return cache.computerIfAbsent(vault, vault::verySecurePassword)
    }    
    
} 

// 19.
// 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> rawPassword;
    private final Vault vault;
    
    private SecurePassword(Vault vault) {
        this.rawPassword = Suppliers.memoize(vault::verySecurePassword);
    }
    
      public String raw() {
        return raw.get();
    }    
    
} 
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment