AUTHOR
TO
DATE
Methodsin comprehensible code
Writing clean common sense method
25.08.2016Miklós Csere
software engineer @Netcentric
AgendaContext
To OOP || !OOP
Imperative vs Declarative
Temporal coupling
NULL == EVIL; STATIC == EVIL;
Defensive Programming
Mutable / Immutable
Size matters
Adobe Experience Manager Perspective
00
01
02
03
04
05
06
07
Think before you code You can either start coding right away or you can sit down, relax, and start thinking.
0Context Q: Should I apply (something)? A: It depends.
Before applying a programming paradigm / patternwe have to think about what we want to achieve:
❖ AEM Component (Java Bean)❖ DTO (POJO)
❖ OSGI Service
❖ HTTP Request Handlers❖ Event Handlers
❖ Helpers (Factories, Builders, Transformers, Static ...)
Context
Then we have to think of what should be solved in the current context:
❖ AEM Component, DTO → data transfer
❖ OSGI Service → business logic
❖ HTTP / Event Handlers → call the services above
❖ Helpers (factories, builders, proxies, static ..etc) ➢ → business logic independent, tested and
immutable
Context
What is actually going on?
Context
Context
AEM Component- Has logic- Cannot be extended
Context
OSGI Service- Hard to test- Many
responsibilities
What we could have instead?
❖ Extendable components❖ Testable business logic ❖ Comprehensible objects & methods❖ Separation of concerns❖ SOLID OOP❖ TDD, DDD, BDD
What we could give up on? ❖ Procedural programming → but this will take a while❖ Tight coupling and low cohesion❖ Headaches!
Context
AEM Component
Context
We have a teaser component that can be reused under different circumstances.
Context
Sling Model with no logic
HTTP Servlet - no logic- reads the input, calls a service, writes the answer
Context
3rd party requests and message translation are the responsibility of dependencies Easy to use fake for testing
OSGI Service
This class takes care only of business logic
1OOP || !OOP
Are we actually doing OOP?
Procedural code gets information then makes decisions. Object-oriented code tells objects to do things. Where is the OOP?
Component {void init(){
this.callPrivateMethod();this.osgiService.callPublicMethod();callStaticMethod();
if/else/switch statements
this.osgiService.callPublicMethod();this.callPrivateMethod();
} }
OOP || !OOP
OOP || !OOP
Not really OOP?
Great encapsulation, huh?
Context
Anemic Domain Model:
objects, many named after the nouns in the domain space, and these objects are connected with rich relationships.
there is hardly any behavior on these objects, making them little more than bags of getters and setters.
these models come with design rules that say that you are not to put any domain logic in the the domain objects.
service objects capture all the domain logic. These services live on top of the domain model and use the domain model for data.
Martin Fowler
OOP || !OOP
One source of confusion in all this is that many OO
experts do recommend putting a layer of procedural
services on top of a domain model, to form a Service Layer.
But this isn't an argument to make the domain model void
of behavior, indeed service layer advocates use a service
layer in conjunction with a behaviorally rich domain
model. Martin Fowler
OOP || !OOP
OOP || !OOP
OOP
In AEM the following case is also valid:
POJO as a Sling Model we just represent data and no logic is involved.
OOP || !OOP
2Imperative vs Declarative
vs
An imperative code specifies step-by-step how a problem is solved using a series of statements which change a program’s state.
A declarative code expresses what you want without specifying how. It separates the process of stating a problem from the process of solving it.
Imperative vs Declarative
“I’m by Ikea. How do I get to your house from here?”
An imperative response: Go out of the north exit of the parking lot and
take a left. Get on B-10 south until you get to the Maritim Highway exit. Take
a right off the exit like you’re going to Ikea. Go straight and take a right at
the first light. Continue through the next light then take your next left. My
house is #123.
A declarative response: My address is 123 Immutable Alley, Barcelona
80003
Imperative vs Declarative
Imperative vs Declarative
/*** Return the double value of all even numbers in an array;* A hashtag has to be added at each number;*/
Imperative solution
Imperative vs Declarative
Declarative Solution
Declarative programming is not necessarily "lean and mean". It's not something you probably want to do for a quick and dirty app.
In the long term, the combination of an abstraction layer driven by declarative code backed by application specific imperative code makes for very maintainable and extensible applications.
But most important: test your business logic!
Imperative vs Declarative
Declarative programming:
❖ Minimizes mutability ❖ Reduces state side-effect❖ It leads to more understandable code❖ It is more scalable (easier to maintain)
So that next time we're about to implement some functionality, we
could ask ourselves whether it can be written in a declarative manner.
Imperative vs Declarative
3Temporal coupling
Temporal coupling happens between sequential
method calls when they must stay in a particular order.
This is inevitable in imperative programming, but we
can reduce the negative effect of it just by turning those
procedures into methods.
Temporal coupling
Imperative code
What happens if I add the “#” before doing the math?
Temporal coupling
Declarative code
The magic happens only at the end. We first declare, then we execute.
Temporal coupling
4NULL == EVIL; STATIC == EVIL;
What will this generate?
NULL == EVIL
❖ Unexpected NullPointerException❖ Multiple (random) error handling
❖ Ambiguous variable initialization ➢ Is NULL an Employee ?
NULL == EVIL
❖ Slow failing ➢ Instead of interrupting the execution of the thread
we continue doing processes which might not be necesarry
❖ Mutable and incomplete objects➢ Add a caching layer and you will end up with
inconsistent objects stored
NULL == EVIL
Alternatives❖ Null Object Pattern
❖ Throw an Exception (Fail fast)
NULL == EVIL
❖ Let’s get inspiration from jQuery
Never return null arrays or lists Instead just return an empty collection
NULL == EVIL
Static methods and variables are global. They are an extra hard-coded dependency to your code.
“Unit-testing assumes that I can instantiate a piece of my application in isolation. During the instantiation I wire the dependencies with mocks/friendlies which replace the real dependencies. With procedural programing there is nothing to “wire” since there are no objects, the code and data are separate.”
Miško Hevery,2008
STATIC == EVIL
A static method does more than it is supposed to: Example: class NodeHelper
STATIC == EVIL
Since we are working with JCR, we will find ourselves required to use either recursive or while loops
STATIC == EVIL
But where does it stop?
Hard to reason about when you don’t have a context.
Testing this method is a nightmare
Declarative Solution: ❖ Boundaries❖ Search conditions ❖ Recursion
STATIC == EVIL
5Defensive programming
Classical defensive programming:Ex: if (something != null) → then doSomething();
if (isNumber(input)) → then doSomething();
Defensive programming
Adds unnecessary code to our methods:
❖ Not related to what our method should do❖ Clutters methods with exceptional cases❖ Makes it hard to test all cases (combinations)
Defensive programming
Solution: Decorator Pattern
Smaller objects always mean higher maintainability. Our class will always remain small, no matter how many validations we may invent in the future.
The more things we need to validate, the more validating decorators we will create. All of them will be small and cohesive all together in different variations.
Defensive programming
Defensive programmingDecoratorInterface
Usage
Actual implementation
Alternative: Fail fastIf an invalid call has been made, tell the developer
that something went wrong → You deserve a NPE!No validations. Don’t forget to treat the exception.
Defensive programming
6Mutable / Immutable
Immutable Object → the internal fields (or at least, all the internal fields that affect its external behaviour) cannot be changed.
Mutable / Immutable
Advantages of having immutable classes:
❖ immutable objects are simpler to construct, test, and use
❖ immutable objects are always thread-safe
❖ they help to avoid temporal coupling
❖ their usage is side-effect free (no defensive copies)
❖ identity mutability problem is avoided
❖ they always have failure atomicity
❖ they are much easier to cache
❖ they prevent NULL references, which are bad
Mutable / Immutable
Programmers are often reluctant to employ immutable objects,
because they worry about the cost of creating a new object as
opposed to updating an object in place.
The impact of object creation is often overestimated, and can be
offset by some of the efficiencies associated with immutable objects.
These include decreased overhead due to garbage collection,
and the elimination of code needed to protect mutable objects from
corruption.
https://docs.oracle.com/javase/tutorial/essential/concurrency/immutable.html
Mutable / Immutable
Advice:
Always try to minimize mutability of a class
Few exceptions: Classes that are not part of your domain (Builders, Factories)
Mutable / Immutable
7Size matters
Fact:
In World of Warcraft the C++ file containing the code related to the Lich King has 3210 lines. All cohesive and low coupled.
Constructors Must Be Code-Free
Only declarations are allowed.
Doing operations is not their responsibility
Have only one primary constructor
A primary constructor is the one that constructs an object and encapsulates other objects inside it.
Secondary constructors just call the primary one using this (..)
Size matters - methods
Methods must be smallHow small?
“The first rule of [methods] is that they should be small. The second rule of [methods] is that they should be smaller than that”
(Robert C Martin - Clean Code)
“[Methods] should not be 100 lines long.[Methods] should hardly ever be 20 lines long.”
Size matters - methods
Block and indentingMethods should not be large enough to hold nested structures.
The indent level of a function should not be greater than two.
Do One Thing
“Methods should do one thing. They should do it well. They should do it only.”
(Robert C. Martin - Clean Code)
Have no side effectsRemember the session.save() example above?
Size matters - methods
Redundant variables are bad
“The more variable names I have to remember, the longer it takes to digest the code”
(Yegor Bugayenko - Elegant Objects)
Size matters - methods
Composite names smell like bad code
Eg: netcentricEmployeeSomeProjectGPN;
Reading code from Top to Bottom
“We want every function to be followed by those at the next level of abstraction so that we can read the program, descending one level of abstraction at a time as we read down the list of functions.” (Clean Code)
Switch Statements
Should only be used either in a factory or together with a factory.
Size matters - methods
Method arguments
“The ideal number of arguments for a [method] is zero (niladic). Next comes one (monadic), followed closely by two (dyadic). Three arguments (triadic) should be avoided where possible. More than three (polyadic) requires very special justification—and then shouldn’t be used anyway.” (Robert C Martin - Clean Code)
Size matters - methods
Output argumentsBest argument to return is: thisPrefer to throw exceptions over returning error codes.Only return a different
object if you have no Input arguments
Check fluent interfaces →
Size matters - methods
Q: How many methods should a class have?A: As less as possible.
A number between 5 - 8 public methods is mentioned in several places.
Every public method must implement an interface!
Q: How many lines of code should a class have?A: As less as possible.
A number between 100-200 is mentioned as a limit in several places.
Size matters - interface & classes
Q: How many private methods should a class have?A: Put everything in a class that belongs to its responsibilities.
I dislike having many private methods, I think they should be refactored either into decorators or external dependencies of the class.
Size matters - interface & classes
Favor Composition over Inheritance
Size matters - interface & classes
Evolve interfaces & create traits using default methods.
Con: Today we are unable to declare a protected default method
Size matters - interface & classes
❖ Consider your context when writing code ❖ Consider if your class might be extensible (Component)❖ Favor OOP over Procedural Programming
➢ Even if you are not using Domain Driven Design➢ Even if you have an AnemicDomainModel
❖ Try to write declarative code➢ We spend more time reading code than writing it
❖ Take care of temporal coupling❖ Don’t return NULL❖ Don’t use Static Methods❖ Defensive Programming can be done with decorators❖ Minimize mutability
Summary
❖ Think about your code as a final product that you will unveil to the world.
❖ Don’t just write code! Design it!
❖ Think about the UX of the developers using your code. That might be you in 2 years!
DRY → don’t repeat yourselfKISS → keep it simple (stupid)YAGNI → you ain’t gonna need it
Summary
Write beautiful code