- Slides: https://sizovs.net/jninja
- Exercises: https://bit.ly/java_practice
- Homework: https://github.com/sizovs/awesome-homework-for-java-developers
- 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)
- Kent Beck (the creator of eXtreme Programming and TDD, coined the term "reveal intent, hide implementation")
- https://www.youtube.com/watch?v=t1bJ3W5Uc_k (Ru)
- https://www.youtube.com/watch?v=99VFdX1WS7o (En)
- Stan4j (http://stan4j.com/advanced/)
- Java Application Architecture
- Speedment
- Optimistic Locking
- Pessimistic Locking
- Default DB locking
- https://medium.com/@rakyll/things-i-wished-more-developers-knew-about-databases-2d0178464f78
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.
- SRP (Single Responsibility Principle)
- OCP (Open Closed Principle)
- LSP (Liskov Substitution Principle)
- ISP (Interface Segregation Principle)
- DIP (Dependency Inversion Princiciple)
// 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();
    }    
    
}