Last active
September 29, 2020 13:34
-
-
Save sizovs/1030c50c4bd3fe0c7cf974820ecb6987 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
http://bit.ly/playtech_1 | |
#### Slides #### | |
sizovs.net/jninja | |
#### Books to read #### | |
- Clean Code (Martin) | |
- Clean Coder (Martin) | |
- The Software Craftsman (Mancuso) | |
- Head First Design Patterns (Sierra) | |
- Head First Object Oriented Analysis & Design (McLaughlin) | |
- 99 Bottles of OOP (Metz) | |
- Elegant Objects (vol 1) (Bugayenko) | |
- Refactoring (Fowler) | |
- Growing Object-Oriented Software Guided by Tests (Freeman, Pryce) | |
- Secure By Design (Johnsson) | |
- Domain-Driven Design (Evans) #architecture | |
- Patterns of Enterprise Application Architecture (Fowler) #architecture | |
- Release It! (Nygard) 2nd edition | |
#### Exercises #### | |
bit.ly/java_practice | |
#### SOLID principles #### | |
https://vimeo.com/60193259 (video) | |
#### SQL stream library #### | |
https://github.com/speedment/speedment | |
#### Package dependencies #### | |
There was a nice question about choosing the dependency "direction" between classes. To answer this question, you first have | |
to understand why and how to manage dependencies between packages. Here are some materials: | |
// My old but still relevant talk (in Russian): https://www.youtube.com/watch?v=t1bJ3W5Uc_k | |
// http://stan4j.com (see Structural Analysis section) | |
// There is also a nice book called Java Application Architecture by Kirk Knoernschild | |
#### There were two questions about concurrent hash map behaviour #### | |
❶ The first question was about atomicity guarantees for computeIfAbsent() method. | |
According to Javadocs, the method will be called once per key, so you can safely use it for memoization. | |
Here the excerpt: | |
// If the specified key is not already associated with a value, | |
// attempts to compute its value using the given mapping function and enters it into this map unless null. | |
// The entire method invocation is performed atomically, so the function is applied at most once per key. | |
// Some attempted update operations on this map by other threads may be blocked while computation is in progress, | |
// so the computation should be short and simple, and must not attempt to update any other mappings of this map. | |
❷ Another was related to initial capacity and load factor: | |
For the following hash map, upon the first put() invocation, | |
resizing happens twice – at the beginning of put() AND after the put(): | |
private static final HashMap<String, String> hashMap = new HashMap<>(1); | |
With load factor 1.0, only initial resizing happens: | |
private static final HashMap<String, String> hashMap = new HashMap<>(1, 1.0f); | |
I haven't looked into the recent concurrent hash map implementation, | |
but I would expect the same semantics, as it's has been the same for years. | |
#### Homework #### | |
https://github.com/sizovs/awesome-homework-for-java-developers | |
#### 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(Post::isFree)) // faux-pax / noexceptions / jOOL | |
.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, Loans loans); | |
class Loans { | |
Map<LocalDateTime, Id> existingLoansByDate; | |
} | |
class Application { | |
BigDecimal amount; | |
int term; | |
} | |
// 4. Find a missing domain objects and reify (thingify) it. | |
class MortgageRiskService { | |
BigDecimal calculateRisk(MortageApplication mortgageApplication) { | |
... | |
} | |
boolean isTolerable(BigDecimal risk) { | |
... | |
} | |
boolean areRisksEquivalent(BigDecimal oneRisk, BigDecimal otherRisk) { | |
... | |
} | |
} | |
class Risk { | |
private final BigDecimal risk; | |
Risk(MortageApplication mortgageApplication) { | |
this.risk = somehowCalculateTheRisk(mortgageApplication); | |
} | |
boolean isTolerable() { | |
} | |
boolean equals(Risk risk) { | |
} | |
} | |
// 5. Find a missing domain objects and reify (thingify) it. | |
class BankruptcyProbabilityCalculator { | |
BigDecimal calculate(Business business) { ... } | |
} | |
class BankruptcyProbabilityUtils { | |
boolean isHigh(BigDecimal decimal) { ... } | |
} | |
ee.playtech.bankruptcy; | |
class Probability { | |
Probability(Business business) { | |
this.probability = ... | |
} | |
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 { | |
CsvFile(Path path) { | |
this.path = path; | |
} | |
Stream<T> lines() { | |
} | |
} | |
csvFile.lines().forEach(System.out::println); | |
// 7. Replace a procedural design (and an agent noun Pinger) with an object | |
interface Pinger { | |
void sendPing(); | |
} | |
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); | |
} | |
class Iso1234Money { | |
Iso1234Money(Money money) { | |
} | |
@Override | |
public toString() { | |
// very boring enterprise format | |
} | |
} | |
class PrettyMoney { | |
PrettyMoney(Money money) { | |
} | |
@Override | |
public toString() { | |
// asdada | |
} | |
} | |
System.out.println("The amount is: " + prettyMoney); | |
// 9. Turn XmlMarshaller into a class Xml | |
// + Make the class generic (decouple it from Invoice) | |
// + Use type inference | |
// + Use try-with-resources block | |
class XmlMarshaller { | |
byte[] marshallToXml(Invoice invoice) { | |
ByteArrayOutputStream outStream = new ByteArrayOutputStream(); | |
JaxbMarshaller jaxbMarshaller = new JaxbMarshaller(Invoice.class); | |
jaxbMarshaller.marshallObject(invoice, outStream); | |
byte[] resultXml = outStream.toByteArray(); | |
IOUtils.closeQuietly(outStream); | |
return resultXml; | |
} | |
} | |
class Xml<T> { | |
Xml(T entity) { | |
this.entity = entity; | |
} | |
byte[] bytes() { | |
try (var result = new ByteArrayOutputStream()) { | |
var jaxb = new JaxbMarshaller(entity.getClass()); | |
jaxb.marshallObject(entity, outStream); | |
return result.toByteArray(); | |
} | |
} | |
} | |
// 10. /validate/ method is a query (returns the result), but it sounds like an action. Fix it! | |
interface Input { | |
boolean validate(); | |
} | |
interface Input { | |
boolean isValid(); | |
} | |
// 11. ditch setters and provide domain-specific commands. | |
class BankAccount { | |
enum Status { | |
CLOSED, OPEN | |
} | |
private final Status status; | |
void setStatus(Status status) { | |
this.status = status; | |
} | |
} | |
class BankAccount { | |
enum Status { | |
CLOSED, OPEN | |
} | |
private Status status; | |
void close() { | |
this.status = Status.CLOSED; | |
} | |
void open() { | |
this.status = Status.OPEN; | |
} | |
} | |
// 12. ditch a setter and provide a domain-specific command. | |
class User { | |
private final Permissions permissions; | |
void setPermissions(Permissions permissions) { | |
this.permissions = permissions | |
} | |
} | |
class User { | |
private final Optional<Permissions> permissions = Optional.empty(); | |
void grant(Permissions permissions) { | |
this.permissions = Optional.of(permissions); | |
} | |
void revokePermissions() { | |
this.permissions = Optional.empty(); | |
} | |
} | |
// 13. Because of CQS, a naming conflict might arise. Fix it! (no getters & setters allowed). | |
user.ban() // returns user's ban and the corresponding information (if any) | |
user.ban() // bans a user | |
user.putABan(); | |
user.currentBan(); | |
// 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" | |
interface AnonymousUserAuthenticator { | |
String authenticate(String username, String password) throws UserDoesNotExistException; // returns authentication token | |
} | |
class AuthenticationToken { | |
AuthenticationToken(Credentials credentials) { throws UserDoesNotExistException { | |
} | |
public asString() | |
} | |
} | |
token = new AuthenticationToken(credentitals); | |
// 16. fix naming issues | |
interface Suite { | |
interface Test { | |
void print(); | |
boolean isSuccessful() | |
} | |
void runAndWait(); | |
Collection<Test> tests(); | |
} | |
suite.runAndWait() | |
😱 suite.tests().filter(not(Test::isSuccessful)).forEach(Test::prettyPrint); | |
🦄 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; | |
String fullName() { | |
return nickName | |
.map(nick -> firstName + " <" + nick + "> " + lastName) | |
.orElseGet(() -> firstName + " " + lastName); (1) | |
// .orElse(firstName + " " + lastName); (2) | |
} | |
} | |
var password = new SecurePassword(vault); | |
// business logic happens | |
password.raw(); // remote call | |
password.raw(); // cache hit | |
password.raw(); // cache hit | |
password.raw(); // cache hit | |
// 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> memoizer = new ConcurrentHashMap<>(1, 1.0f); | |
private SecurePassword(Vault vault) { | |
this.vault = vault; | |
} | |
public String raw() { | |
return memoizer.computeIfAbsent(vault, vault::verySecurePassword); | |
} | |
} | |
class SecurePassword { | |
private final Supplier<String> passwordSupplier; | |
private SecurePassword(Vault vault) { | |
this.passwordSupplier = Suppliers.memoize(vault::verySecurePassword); | |
} | |
public String raw() { | |
return passwordSupplier.get(); | |
} | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment