Skip to content

Instantly share code, notes, and snippets.

@sizovs
Last active December 3, 2020 07:56
Show Gist options
  • Save sizovs/ea2fad6e48d6298983b925da95a1716a to your computer and use it in GitHub Desktop.
Save sizovs/ea2fad6e48d6298983b925da95a1716a to your computer and use it in GitHub Desktop.

Люди, которых упомянали во время курса

  • Kent Beck – создатель eXtreme Programming. У него есть две бомбические книги: "TDD by Example" и "XP Explained."
  • Sandi Metz – придумала термин "echo chambers". Написала отличную книгу по ООП "99 Bottles of OOP." Еще стоит посмотреть все ее доклады с конференций.
  • Martin Fowler – написал книгу Refactoring и Patterns of Enterprise Application Architecture (PEAA). Вторую следует почитать, если хотите систематизировать знания по классическим паттерам архитектуры enterprise приложений.
  • Michael Nygard – написал книгу по нам и принципам создания отказоустойчивых систем "Release It!"
  • Егор Бугаенко – написал книгу "Elegant Objects". На мой взгляд первое издание книги – это лучшая книга по ООП, но слегка "религиозная". Нужно фильтровать.

Еще книги

  • Growing Object-Oriented Software Guided by Tests – лучшая книга по TDD.
  • Domain-Driven Design (Evans) - лучшая книга по DDD (хоть и достаточно тяжелая для восприятия)

Если как и я вы любите читать, то можете найти меня на сайте Goodreads: https://www.goodreads.com/user/show/24705191-eduards-sizovs. Там я даю оценку книгам по программированию (и не только). Все книги с 5 звездами очень крутые.

Принципы SOLID

  • SRP
  • OCP
  • LSP
  • ISP
  • DIP

По SOLID у меня есть доклад: https://vimeo.com/60193259. Это мой первый публичный доклад, поэтому не судите строго. До сих пор считаю что тему SOLID раскрыл успешно и там есть классные примеры :-)

Разные разности

  • InheritableThreadLocal
  • ReactiveTransactionManager (Spring)
  • Structure 101 - инструмент для управления зависимостями между пакетами

Видео

  • Code Structural Analysis или видео про принципы управления зависимостями между пакетами: https://www.youtube.com/watch?v=t1bJ3W5Uc_k.
  • Keep It Clean или видео про "чистую" архитектуру джава приложений. Так как многих из вас тема архитектуры интересует, то очень советую посмотреть этот доклад. https://www.youtube.com/watch?v=sND1AR7Q_T0

На остальные видео есть ссылки в слайдках.

Библиотеки

Список библиотек, которые я упомянул во время тренинга можете найти вот здесь: https://sizovs.net/2020/11/24/java-libraries-i-like. В этом списке есть и другие библиотеки, которые во время тренинга мы не рассматривали. Обязательно пробегитесь по списку. Особенно советую поиграться с библиотекой ArchUnit, которую мы не успели рассмотреть во время тренинга.

Так же мы говорили, что в Скале есть класс Try. Вот популярная Java библиотека, в которой тоже есть класс Try. И еще удобный паттерн матчинг: https://www.vavr.io/

Демо приложение

  • По этой ссылке лежит приложение, в котором используется библиотека PipelinR вместе со Спрингом: https://github.com/sizovs/unsuck-java. Там еще есть много разных интересных примеров: юнит тесты на Spock, Transactional Outbox (это паттерн когда мы сохраняем команды для дальшейнего исполнения в SQL базе данных, а не в очереди типа ActiveMQ). Там же есть примеры использования jOOQ, Dagger, и еще много чего.

Что не успели

  • Паттерн "Printers". Смотрим класс BankStatement, метод print() в демо приложении. Суть в том, что объект сам может себя сериализовать в Json.
  • Паттерн "Method Object". Смотрим класс BankStatement и находим вложенные классы EnforceWithdrawalLimits и EnforcePositiveBalance. Этот паттерн помогает сгруппировать приватные методы.
  • Доступ к данным без репозиториев. Мы узнали что такое ДАО и что такое репозиторий. Но есть вариант вообще не создавать репозитории, а просто писать файндер-классы (см. класс BankAccountByIban). А для записи в базу использовать DSLContext (jOOQ), SessionFactory (Hibernate), или EntityManager (JPA). Несмотря на то, что мы немного "скауплим" доменную модель с persistence, на практике полноценный декауплинг или невозможен или непрактичен.

В целом покрыли бОльшую часть материала! : ) Вы - молодцы!

Что дальше

  • Давайте дружить на Линкедин (https://www.linkedin.com/in/eduardsi) или в Твиттере https://twitter.com/eduardsi.
  • Если есть желание, можете выполнить домашку. Вариант 1: https://github.com/sizovs/jninja_challenge Вариант 2: https://github.com/sizovs/awesome-homework-for-java-developers
  • Приезжайте в Ригу на ДевТернити https://devternity.com. Это хорошая возможность нахватать новые инсайты, пообщаться с коллегами из Эстонии (они часто приезжают) и просто хорошо провести время. На сегодняшний день, ДевТернити по многим рейтингам считается одной из лучших конференций в Европе.
  • Если тренинг понравился, зовите меня еще :-) Всегда рад помочь.

Ссылка на задания

http://bit.ly/java_practice

Решение заданий

// 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().function(Port::free)) // NoException
        .findFirst();



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

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

class Loans {

}

class ExistingLoans {
  Loans loans;
}

class Application {
  BigDecimal amount, 
  int term
}

        

// 4. Find a missing domain objects and reify (thingify) it.
class MortgageRisk {

  private final BigDecimal risk;
  
  MortageRisk(MortageApplication mortgageApplication, MortgagePolicy policy) {
    this.risk = calculate(mortgageApplication);
  }
  
  boolean isTolerable() {
    ...
  }
  @Override
  boolean equals(@Nullable MortgageRisk obj) {
     if (obj == null) {
      return false;
     }
     if (obj instanceof MortgageRisk other) {
       return this.risk.equals(other.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) { ... }
}        


// Structure 101

//
ua.playtech.app.business
  class Business
  
ua.playtech.app.insolvency 
  class BankruptcyProbability


BankruptcyProbability bankruptcyProbability = business.bankruptcyProbability() {

}

class BankruptcyProbability {
  BankruptcyProbability(Business business) {
    this.probability = ...
  }
  
  boolean 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 CsvFile<T extends Line> { 
    CsvFile(Path location) {
      this.location = location;
    }
    Stream<T> lines() { 
     // StreamEx
    } 
} 
  
var csvFile = new CsvFile(...);
csvFile.lines().forEach(...)

        
// 7. Replace a procedural design (and an agent noun Pinger) with an object
interface Host { 
    Ping ping(int something); 
}

        

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

class PrettyMoney {
  PrettyMoney(Money) {
  }
  @Override
  public toString() {
    return ...;
  }  
}

class Iso142FormattedMoney {
  Iso142FormattedMoney(Money) {
  }
  @Override
  public toString() {
    return ...;
  }
}
var money = new Money(100.00);
System.out.println("You have: " + new Pretty(money) + ". OK?");

money.format();
money.formatted(Format format);

        

// 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() { 
        try (var out = new ByteArrayOutputStream()) {
          JaxbMarshaller jaxb = new JaxbMarshaller(entity.getClass()); 
          jaxb.marshallObject(entity, out); 
          return out.toByteArray(); 
        }
    } 
}
        

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


        
// 11. ditch setters and provide domain-specific commands.
class BankAccount {
    
    enum Status {
        CLOSED, OPEN
    }
    
    private Status status;
   
    
    public void open() {
      this.status = OPEN;
    }
    
    public void close() {
       this.status = CLOSED;
    }
}        

// 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 = Permissions.NONE;
    }
}

user.revoke(permissions);
user.grant(permissions);        
        
// 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"
class AuthenticationToken {
    AuthenticationToken(Credentials credentials) { throws UserDoesNotExistException
    }
    public asString() {
      ...
    }
}


        
        
// 16. fix 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)

        

// 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() {
      var nick = nickName.map(nick -> " <" + nickname + "> ").orElse(" ");
      return firstName + nick + lastName;
   
      return nickname
        .map(nick -> firstName + " <" + nickname + "> " + 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);                
}

var password = new SecurePassword(vault);

password.toString(); // 23424
password.toString(); // 24eqwdara
password.toString();
password.toString();
password.toString();

// 20.
// 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;
    }
    
    @Override
    public String toString() {
        return cache.computeIfAbsent(vault, vault::verySecurePassword);
        
    }    
    
}        


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