session roadmap
- Week 1: Software Craftsmanship
- Week 2: Software Craftsman journey
- Week 3: TDD/BDD
- Week 4: Spring ecosystem I
- Week 5: Spring ecosystem II
- Week 6: REST
- To be completed/ confirmed ...
Software craftsmanship
Carlos Cornejo
Why we're here?
- Share experiences and tips gathered while developing in different contexts.
- Review foundations, principles and concepts of OOD.
- Tools, techniques and best practices that can help while designing software.
- Understand how to couple modelling and testing to drive the development of software.
- Think and understand different approaches to test software.
Initial thoughts
"Programs are read more than they written"
"The way code is written defines a developer"
"There is no such thing as done"
....that's why should we have friday's refactoring sessions
"A good developer is not someone that writes code that no one understands .... but someone that writes code in a fashion that any developer can"
Make sure that the code is readable in a way that the intentions and functionality of it are clear
roadmap
- Theory session
- Web of Objects
-
SOLID principles
- Other SD principles
- Practice session
- Refactoring
- Examples
- Types of tests
- Verification vs Design
- BDD
- Unit Testing
- State vs Interaction
- Tools
-
Conclusions
growing object-oriented software
"Object-oriented design focuses more on the communication between objects than
on the objects themselves"
"An object-oriented system is a web of collaborating objects"
A web of objects
An object communicates by messages: It receives messages from other objects and reacts
by sending messages to other objects as well as, perhaps, returning a value or exception to the original sender
By having a highly cohesive and loosely coupled system
any change into the system’s behaviour it's easier because we can focus on what we want it to do, not how.
Coupling and Cohesion
Coupling and cohesion are metrics that (roughly) describe how easy it will be to
change the behaviour of some code.
Elements are coupled if a change in one forces a change in the other.
“Loosely” coupled features are easier to maintain.
An element’s cohesiveness is a measure of whether its responsibilities form a single and meaningful unit
"code must be loosely coupled and highly cohesive—in other words, well-designed" [Uncle Bob]
CRC Cards
(Candidate, Responsibilities, Collaborators)
CRC Cards
We try to think about objects in terms of roles, responsibilities and collaborators.
"An object is an implementation of one or more roles; a role is a set of related responsibilities; and a responsibility is an obligation to perform a task or know information. A collaboration is an interaction of objects or roles (or both)."
Example: How would you model a restaurant?
solid principles
solid principles
Robert 'Uncle Bob' Martin introduced it in 1995
What is S.O.L.I.D.?
S.O.L.I.D. is a collection of best- practice, object-oriented design principles which can be applied to your design
What is useful for?
- loose-coupling
-
higher maintainability
- intuitive location of interesting code
- high cohesion
- encapsulation
- focus in what to do not how to do it
solid principles
SRP: Single Responsibility Principle
solid principles
Single Responsibility Principle (SRP)
"An object should have only one reason to change or exist"
Each complex problem cannot easily be solved as a whole.
It is much easier to first divide the problem in smaller sub-
problems.
"Divide et imperat" (Divide and Conquer)
This principle applies to classes and methods.
solid principles
Open-Closed Principle (OCP)
"We should write our modules assuming that they can and should be extended, without requiring them to be modified"
solid principles
Liskov Substitution Principle (LSP)
aka design by contract
In a nutshell:
"If you have a base class BASE and subclasses SUB1 and SUB2, the rest of your code should always refer to BASE
and NOT SUB1 and SUB2"
"Prefer composition over inheritance whenever possible... though"
solid principles
Interface Segregation Principle (ISP)
“many client-specific interfaces are better than one general purpose interface”
Think … to create specialised interfaces serving specific requirements,
rather than finding the mother of all interfaces, that will service the
universe.
"clients shouldn't be forced to implement interfaces they don't use or even care"
solid principles
Interface Segregation Principle (ISP)
solid principles
Interface Segregation Principle (ISP)
solid principles
Dependency Inversion Principle (DIP)
“depend on abstractions (interfaces), not on corrections (implementations)”
Try to get the most of IoC containers and delegate to them for the creation of the collaborators
solid principles
Conclusions and Benefits
- Low Coupling
- By abstracting many of our implementation needs into various interfaces and introducing SOLID concepts, we’ve created a system that has very low coupling. Many of these individual pieces can be taken out of the system with little to no spaghetti mess trailing after it.
- High Cohesion
-
As a result of low coupling and SRP; we have a lot of small pieces that can be stacked together to create something larger and more complex. DIP also allows to tie the various blocks together by depending on an abstraction with different implementations.
solid principles
Conclusions and Benefits
- Encapsulation
- True encapsulation is not just making fields private and hiding data from external objects, it’s hiding implementation details from other objects, depending only on the abstractions and expected behaviors of those abstractions.
- LSP, DI, and SRP all work hand in hand to create true encapsulation in the new
project structure.
- We’ve encapsulated our behavioral implementations in many individual objects,
preventing them from leaking into each other.
Other SD principles
-
KISS (keep it simple and stupid)
-
so we make it easy to reuse
-
DRY (don't repeat yourself), DIE (Duplication is Evil), etc..
- interdeveloper duplication, lack of communication or knowledge of the app?
-
this is very likely to happen in multi-tier architectures.
- SRP?
- when applied successfully, a modification of any single element of a system does not require a change in other logically unrelated elements.
OTHER SD PRINCIPLES
- YAGNI (You ain't gonna need it)
- Always implement things when you actually need them, never when you just foresee that you need them
- OCP?
- YAGNI helps to not over engineer things.
CLEAN CODE
"Beauty of style and harmony and grace and good rhythm depends on simplicity" Plato The Republic
“Always leave the camp ground cleaner than you found it.” the boy scouts rule
Clean Code
Use Intention-Revealing Names
int d; // elapsed time in days
int elapsedTimeInDays;
int daysSinceCreation;
int daysSinceModification;
int fileAgeInDays;
Use Pronounceable/Searchable Names
class DtaRcrd102 {
private Date genymdhms;
private Date modymdhms;
private final String pszqint = "102";
/* ... */
};
class Customer {
private Date generationTime;
private Date modificationTime;
private final String recordId = "102";
/* ... */
};
CLEAN CODE
Function Names Should Say What They Do Date newDate = date.add(5);
//addDaysTo or increaseByDays
Encapsulate Conditionals if (timer.hasExpired() && !timer.isRecurrent())
if (shouldBeDeleted(timer)) //preferred
Avoid Negative Conditionals
if (!buffer.shouldNotCompact())
if (buffer.shouldCompact()) //preferred
CLEAN CODE
Functions Should Do One Thing
public void pay() {
for (Employee e : employees) {
if (e.isPayday()) {
Money pay = e.calculatePay();
e.deliverPay(pay);
}
}
}
//preferred
public void pay() {
for (Employee e : employees)
payIfNecessary(e);
}
private void payIfNecessary(Employee e) {
if (e.isPayday())
calculateAndDeliverPay(e);
}
private void calculateAndDeliverPay(Employee e) {
Money pay = e.calculatePay();
e.deliverPay(pay);
}
CLEAN CODE
Encapsulate Boundary Conditions
if(level + 1 < tags.length)
{
parts = new Parse(body, tags, level + 1, offset + endTag);
body = null;
}
int nextLevel = level + 1;
if(nextLevel < tags.length)
{
int delay = offset + endTag;
parts = new Parse(body, tags, nextLevel, delay);
body = null;
}
clean code
Keep Configurable Data at High Levels
public class AnyClass
{
public static final String DEFAULT_PATH = ".";
public static final String DEFAULT_ROOT = "FitNesseRoot";
public static final int DEFAULT_PORT = 80;
public static final int DEFAULT_VERSION_DAYS = 14;
...
}
CLEAN CODE - Comments
Comments Do Not Make Up for Bad Code
Explain Yourself in Code
TODO Comments are dangerous as they may stay in the code if the TODO-er doesn't pick it up..
This a topic open for discussion
CLEAN CODE - Formatting
Code formatting is important, is about communication!!
Also open for discussion within the team:
-
Vertical Formatting
- Vertical Density
- Vertical Distance
- Vertical Ordering
- Horizontal Formatting
CLEAN CODE - Defensive Programming (JSR305)
Annotations on arguments and methods
JSR-305 is a Java Specification Request intended to improve the effectiveness of static analysis tools.
Declare for visibility the state of an argument or possible state of a response from a method. Will help developers read code clearly.
For idea users: FindBugs-IDEA
CLEAN CODE - Automate Your
Coding Standard
- Make sure code formatting is part of the build process.
- Use static code analysis tools to scan the code for unwanted anti-patterns.
-
Do not only measure test coverage but break the build if test coverage is too low.
Finally, the coding standard should be dynamic rather than static based the team feedback.
Clean code - takeaways
The Boy Scout Rule
Beauty Is in Simplicity
Code Is Design
Code Layout Matters
Continuous Learning
Don’t Be Afraid to Break Things
Encapsulate Behaviour, Not Just State
Improve Code by Removing It
Know Your IDE
Only the Code Tells the Truth
Prefer Domain-Specific Types to Primitive Types
refactoring
"Refactoring is the process of changing a software system in such a way that it does not alter the external behaviour of the code yet improves its internal structure"
"refactoring is to code what the gym is to a body builder == necessary" anonymous
"It is a disciplined way to clean up
code that minimizes the chances of introducing bugs"
... In essence when you refactor you are
improving the design of the code after it has been written.
The drawback of refactoring is that you have to have a solid set of tests to not introduce bugs and have problems with your boss
REFACTORING
When Should You Refactor?
When You Add Function
When You Need to Fix a Bug
As You Do a Code Review
When shouldn't you refactor?
- When you don't have tests to support your refactoring
- When you should rewrite from scratch instead
- When you are close to a deadline
-
Avoid the temptation to rewrite everything
But these scenarios shouldn't stop you doing what you should so:
- Create your branch
- Ultimately create technical debt task
- Do it in your breaks ;-)
REFACTORING
Bad Smells in Code
"If it stinks, change it" .. by Kent Beck and Martin Fowler
REFACTORING
Bad Smells in Code
Duplicated Code
- The same code structure in more than one place, it's telling you that your program will be better if you find a way to unify them.
- Having n-plicated chunks of code is likely to introduced bugs if you forget to make the changes in all of them
-
Extract method and pull up variable/field are your best buddies here!
REFACTORING
Bad Smells in Code
Train wreck
SelectionUtils.processDisplayOdds(eachEvent.getContent().getFirstBook().getContent()
.getSelections());
SelectionUtils.processDisplayOdds(eachEvent.getCouponSelections()); if (selectionDisplayOptions.getMarketTypeIdsForSelectionOrderingByName()
.contains(event.getContent().getFirstBook().getContent().getMarketTypeId())) {
SelectionUtils.sortSelections(event.getContent().getFirstBook().getContent()
.getSelections(), SelectionSortType.BY_NAME);
}
if (selectionDisplayOptions.containsCouponMarketTypeId(event.getCouponMarketTypeId())) {
SelectionUtils.sortSelections(event.getCouponSelections(), SelectionSortType.BY_NAME);
}
REFACTORING
Bad Smells in Code
Train wreck
Code in the language of your domain
and make your code more readable
tip: A train wreck access is telling you to add a method to your interface
It's much easier to test as it just needs one stubbing
... And please don't break the encapsulation of your object
REFACTORING
Bad Smells in Code
Long list of method params
BetSlipUtils.addSelectionToBetSlip(
quickbetSlip, user, site, betSlipServices, sbBusinessLogic, betSelectionMap.get("selectionId"), betSelectionMap.get("marketId"),
betSelectionMap.get("eventId"), betSelectionMap.get("priceUp"), betSelectionMap.get("priceDown"), betSelectionMap.get("americanOdds"),
betSelectionMap.get("decimalOdds"), betSelectionMap.get("handicap"), betSelectionMap.get("handicapId"), betSelectionMap.get("eachWayPlaceTerm"), betSelectionMap.get("eachWayReduction"));
REFACTORING
Bad Smells in Code
Long list of method params
- One of goals in OO programming should be to encapsulate a program's data as much as possible into objects.
- Long parameter lists are hard to understand, because they become inconsistent and difficult to use.
- Changing them becomes a nightmare.
REFACTORING
Bad Smells in Code
Long list of method params
Do we really need as many parameters?
- is becoming a bad smelling symptom and needs a re-think?
- is this going against SRP?
- Are we using DI as we should?
Maybe a good idea could be to create a Object to represent the state or parameter to use??
TESTING the web of objects
"Object-oriented design focuses more on the communication between objects than
on the objects themselves"
The big idea is “messaging”....
types of tests
What is the testing big picture?
Acceptance
Integration
Unit
types of tests
-
Acceptance: Does the whole system work?
- Open discussion in testing world over the terminology
-
end-to-end
- functional tests
- system tests
- We use “acceptance tests” to help us, with the domain experts, understand and agree on what we are going to build next.
- We also use them to make sure that we haven’t broken any existing features as we continue developing.
- Acceptance tests are a superset of all other tests.
- They are expensive, require a significant amount of time to execute themselves (nightly builds).
types of tests
What happens when different units of work are combined into a workflow?
Integration: Does our code work against code we can't change or don't have control of?
It might be a public framework, such as a persistence mapper, or a library from another team within our organization, web service, etc..
types of tests
Unit test: Do our object do the right thing?
- Focus on single classes and should exists in isolation
- They know nothing about the external world
- A unit test should be independent of any context
- A unit test by definition is quick to run
Verification vs design
Different testing approaches with two opposites beliefs
- Verifiers
- check that their code works, that's the only goal
- if the code is hard to test, they'll resort to any available technique to test it
- sacrifice OOD rules
- modify methods visibility using reflection
- class loading hacks to deal with final of legacy stuff
- everything count to achieve the Holy Grail of testability
Verification vs design
- Designers
- believe that following OO rules is the most important thing to lead to the easily testable code.
- treat tests as an indicator of code health.
- easy-to-write code tests denote sound code.
- Code difficult to test it's treated as a clear sign that the code should be refactored/redesigned.
- these fellas particularly like TDD approach.
- What they most like about coding is testing!
-
in case of legacy are very keen to refactor it to make it more testable.
Verification VS design
The terms designer and verifier have been introduced to show boundaries between testing approaches.
What kind of developer are you?
There is not such a 100% designer or 100% verifier thing, we all fall somewhere in between.
Behaviour driven development
- Programmers wanted to know where to start
- What to test and what not to test
- How much to test in one go
- What to call their tests
- How to understand why a test fails?
BEHAVIOUR DRIVEN DEVELOPMENT
“a second-generation, outside-in, pull-based, multiple-stakeholder, multiple-scale, high-automation, agile methodology.” Dan North
- specifies that product people and developers should collaborate and should specify the behaviour of the system based on user stories and scenarios
- is a specialized version of TDD which focuses on behavioural specifications of software units by creating boundaries (scenarios)
BEHAVIOUR DRIVEN DEVELOPMENT
- The essential idea is to break down a scenario (test in BDD) into three
sections using Cucumber's
DSL gherkin language:
- The given part describes the state of the world before
you begin the behaviour you're specifying in this scenario. You can
think of it as the pre-conditions to the test.
- The when section is that behaviour that you're
specifying.
- Finally the then section describes the changes you
expect due to the specified behaviour (output/s).
Testing and code coverage
100% code coverage isn't the goal
100% sounds awesome but...
does not guarantee the lack of defects—it only guarantees that all of your code was executed
is this what we want?
So rather than obsessing about code coverage, focus your attention on making sure that the tests you do write are meaningful and test the functionality
unit testing
-
How to approach unit testing?
-
Tools
- JUnit
- Mockito
- PowerMock
- Matchers (assertJ)
unit testing
How to approach unit testing?
This is when we start to think about
- Architecture of my system
- SOLID principles
- OOD
- black-boxes with pre/post conditions
- test in isolation
- Do I have collaborators/interactions? Mocking time
UNIT TESTing
HOW TO APPROACH UNIT TESTING?
-
Conventions that could be followed or agreed:
-
Follow BDD approach and describe specifications not a test case
- We are not testing methods we are testing functionality, is this clear?
- Test method names should be sentences
- A simple sentence template keeps test methods focused
ClientDetailsValidatorUnitTest
itShouldFailForMissingSurname
testShouldFailForMissingTitle
UNIT TESTING
HOW TO APPROACH UNIT TESTING?
- Conventions that could be followed or agreed:
- An expressive test name is helpful when a test fails
- The class under test are called instance or sut
- Normally the returning value is called outcome
unit test
state vs interaction
- state verification:
-
Assertions
- assertEquals/NotEquals: Are identical values?
- assertTrue/False: is the condition satisfied?
- assertNull/NotNull: is the object reference satisfied?
- assertSame/NotSame: same instance
- assertArrayEquals: assert array contains same objects with same order
- assertThat: assertions using matchers
UNIT TEST
STATE vs INTERACTION
-
state verification
- Checking expected Exceptions
- usage of Assert.fail
@Test(expected = LoginException.class)
public void shouldThrowErrorExceptionWhenInvalidUser() throws LoginException {
sut.login("error", "password");
fail("it shouldn't reach here");
}
UNIT TEST
STATE VS INTERACTION
-
state verification
- What do we use @Before for?
- is executed before every test.
- should include state shared by all tests
- if we have specific initial state for a test should be removed from @Before and moved inside the test as a test fixture
@Before
public void setUp() {
ukashDepositForm = createDummyUKashDepositForm();
serviceCallResult = new ServiceCallResult();
}
@Test
public void shouldReturnExpectedAllowedMethods() {
// test fixtures
List<DepositMethod> aListOfValidDepositsMethods = listOfValidDepositsMethods();
List<DepositMethods> aListOfAllowedMethods = aListOfAllowedMethods();
UNIT TEST
STATE VS INTERACTION
-
behaviour verification
- we test (verify) how our sut interacts or behaves with other collaborators (dependencies(mocks))
- there is a general misunderstanding when referring to a mock
- a dummy object needs to exists, no collaboration needed
- we use test doubles to act as a collaborators
- Mockito allows us to stub methods a.k.a stub
- an object becomes a mock when is used to verify the interaction with the sut
these guys are used to prepare the environment for testing
UNIT TEST
STATE VS INTERACTION
-
behaviour verification
-
verify: Verifies certain behaviour happened X times
public static <T> T verify(T mock) {
return MOCKITO_CORE.verify(mock, times(1));
}
verify(mockUser).getAccountId();
verify(mockUser, times(0)).getCurrencyId();
verify(model).put(eq("ukash"), eq(TRUE));
UNIT TEST
Tools
@RunWith(MockitoJUnitRunner.class)
@InjectMocks
DepositBusinessLogicImpl sut = new DepositBusinessLogicImpl();
@Mock
ModelMap model
UNIT TEST
Tools
Mockito and BDDMockito
when(site.getTimeZone()).thenReturn(A_VALID_UK_TIMEZONE);
given(site.getTimeZone()).willReturn(A_VALID_UK_TIMEZONE);c`sdv
-
using matchers for method params
when(mock.getTransaction(anyLong(),any(TransactionSearchType.class))
.thenReturn(txList);
- verification of params used for method invocation
verify(model).put(eq("ukashDepositCurrencyId"), anyDouble());
doThrow(new Exception()).when(mock).method(any(UkashDepositData.class));
UNIT TEST
Tools
Mockito and BDDMockito good practices
- Don’t mix with Spring Integration Test
- Don’t mock your model (dummies?)
- Don’t abuse mocks.. code smell?
-
Don’t replace asserts with verify
- stubbing section shouldn't be used as a replacement for verify section
UNIT TEST
Tools
-
PowerMock
- uses a custom classloader and bytecode manipulation to enable mocking of static methods, constructors, final classes and methods, private methods, removal of static initializers and more.
- For TDD-ers and test lovers this is your best buddy when dealing with legacy code ;-)
- Setting up:
@RunWith(PowerMockRunner.class)
@PrepareForTest(SBLoginSecurityServiceImpl.class)
whenNew(SecurityContextLogoutHandler.class)
.withNoArguments().thenReturn(securityContextLogoutHandler);
UNIT TEST
Tools
-
Fluent assertions for java:
- assertJ is a Java library that provides a fluent interface for writing assertions
assertTrue(yoda is instanceof Jedi.class);
assertEquals(foundJedi, yoda);
assertNotEquals(foundSith, yoda);
vs
assertThat(yoda).isInstanceOf(Jedi.class)
.isEqualTo(foundJedi)
.isNotEqualTo(foundSith);
assertThat(newHires).containsOnly("Gandalf", "Arwen", "Gimli");
assertThat(outcome).includes(
entry("stakeMultiplier", new Double(1.0)),
entry("taxType", TOTALSTAKE),
entry("numberOfBets", 1L),
entry("betType", SINGLE),
entry("taxRate", 5.0));
conclusions
- There is no de-facto way of doing things in SD, but some discipline always helps.
- The goal of these best practices or principles is to help you communicate your intentions through your code.
- Self-explaining code is key in our sector as people are very likely to change of companies.... but the code stays.
- Remember:
- If it’s hard to read… it’s hard to test.
- If it’s hard to test… it’s hard to use.
- If it’s hard to test… you’re probably doing it wrong.
- If it’s hard to test… take a look at the design and test it again!
References
-
Growing Object-Oriented Software, Guided by Tests - Steve Freeman and Nat Pryce
- Practical Unit Testing - Tomek Kaczanowski
- Refactoring - Martin Fowler
- 97 Things every programmer should now - Kevlin Henney
- Implementation Patterns - Ken Beck
-
The Pragmatic Programmer - Dave Thomas and Andy Hunt
- Michael Feathers, Martin Fowler, Uncle Bob Martin, Kent Beck, Dan North, Ward Cunningham
- etc..
future topics
- TDD
- Custom Matchers and Argument Captor
- Spring Servlet API mock package
- Power Mock advanced
- Mockito Answers