- 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();
}
}