Journal: Object Calisthenics + TDD + Advent of Code - day 1
Remark about (5) One Dot Per Line
Constructors should count as a dot, as if they were written like this:
ExpensePair.new(...)
final ExpensePair firstPair = new ExpensePair(expenses.get(0), expenses.get(1));
final Expense firstExpense = expenses.get(0);final ExpensePair firstPair = new ExpensePair(firstExpense, expenses.get(1));
final Expense firstExpense = expenses.get(0);final Expense second = expenses.get(1);final ExpensePair firstPair = new ExpensePair(firstExpense, second);
Remark about (5) One Dot Per Line
works well with 7) Keep All Entities Small
this forces me to extract variables
but that quickly bumps into 7) Keep All Entities Small (5 lines/method)
forcing an extract method
or forcing a better design
Remark about (1) Only One Level Of Indentation Per Method
euhm, how do I solve this?
public ExpensePair find2ExpensesSumming2020() {for (ExpensePair pair : getAllPairs()) {if (pair.sumIs2020()) {return pair;}}throw new RuntimeException("Can't find a pair summing to 2020");}
for + if breaks this rule!
I don't want to dive into functional ways of solving this (eg.
return getAllPairs().stream().filter(ExpensePair::pair.sumIs2020).findAny().orElseThrow(() => new RuntimeException("Can't find a pair summing to 2020"))
) because I'm doing Object Calisthenics, not Functional Calisthenics.
Therefore I'm limiting myself to loops instead.
so, how do I solve this rule-breakage ???
It would be cool to have linting, that gives error when I break a rule.
But then I will probably not learn to spot the rule-breakers myself.
Remark about (7) Keep All Entities Small (5 lines/method)
Does this include brackets?
private List<ExpensePair> getAllPairs() {final List<ExpensePair> result = new LinkedList<>();for (Expense first : expenses) {final List<ExpensePair> permutationsForOneExpense = permutationsFor(first);result.addAll(permutationsForOneExpense);}return result;}
the body is 6 lines
but one of those is just a closing bracket.
Should each indentation come at an extra cost?
Remark about (5) One Dot Per Line
local scope method calls should be seen as a dot
result.addAll(permutationsFor(first));
actually is
result.addAll(this.permutationsFor(first));
which violates the rule
and should become
final List<ExpensePair> permutationsForOneExpense = permutationsFor(first);result.addAll(permutationsForOneExpense);
Does this make sense?
It goes agains Martin Fowler's inline function calls instead of creating variables for everything.
"because it improves readability"
What are the benefits of this rule? What behaviours does it drive?
Why?- Exposes the intent hides implementation, telling an object to do something for you instead of asking for its internal representation.- Reduces the amount of knowledge you should have to enable a behavior.- This doesn’t apply to LINQ (Language Integrated Query) and fluent interfaces.
So,
- tell, don't ask
- only touch close collaborators
According to Article: Object Calisthenics - William Durand
you should not chain method calls
except method chaining (eg. builders)
only talk to your immediate friends
So
- don't chain method calls
- only touch close collaborators
So, I have probably been interpreting this rule too strict.
Also earlier, when I did the same for constructors, too strict.
Remark
In Practice: Object Calisthenics + TDD + Advent of Code, I mentioned as a possible refactor idea:
make the change easy (then make the easy change in red/green)
But it's never been hard to make the change...
I wonder if that's the simplicity of the exercise, or the object calisthenics doing their work...
I keep postponing
refactor:
- tests as documentation
Maybe I should make it another rule to check on the 'refactor checklist'
Refactor Checklist
Object Calisthenics remove duplication tests clearly express required functionality
I'm not sure if that would be more something to do at the end.
As it may only be possible to see the patterns, after multiple tests are written?
On the other hand, I kinda wrote my tests in the way I ended refactoring them to:
- multiplication, ignoring finding those that sum to 2020
- find those that sum to 2020, ignoring multiplication
With an acceptance test before and the input.txt test at the end
transition from part 1 to part 2
make the change easy then make the easy change
I want to rename methods to match my new understanding of the domain.
But let's try to first add the functionality with failing tests
and then do that in the refactor
I had the inclination to align some code with other code.
To follow the pattern.
But I reverted that change.
And instead I tried to write a test, to force me to make that change.
Weirdly enough, I was unable to make it green fast.
I kept bumping into other tests failing by hacking it in.
After adding more and more complicated if statements, I gave up and re-reverted the code.
make the change easy then make the easy change
to getter or not to getter ; that's the question
Getters are really useful in green.
Having them everywhere already, is pretty awesome.
Green takes mere seconds!
But I end up with getters I don't use.
Getters I'm not allowed to use even.
So I want to remove them so I'm sure I'm never using them.
But they are so easy during green!
And all these getters throw off code coverage...
Ignoring the unused, generated getters, I'm 100% covered.
Except for the place where I'm actually missing a test!
But it's impossible to find with all the getters dragging the code coverage down...
Maybe I should stop optimising for green.
In green I can relatively easily add @Getter when I need to hack it in.
And then I can easily do a global search to find all @Getter during the refactor phase, because they are not allowed!
One time, I refactored to polymorphism.
The refactor step was pretty huge compared to usual.
This bothers me.
Maybe I can use Book: Refactoring to Patterns - Joshua Kerievsky
I vaguely remember someone mentioning:
safe steps to refactor to patterns using the smaller refactors defined in Book: Refactoring - Martin Fowler
I noticed something:
"remove duplication" as a way to avoid triangulation is pretty awesome.
But it is easy to fall into the trap of adding extra functionality.
I did not write the test for an exception, but just added the exception during one of my "remove duplication" flows.
I think it's an awesome pattern, which makes TDD much more enjoyable.
But there is the risk of over-engineering here.
Code coverage does spot it, so that's something...
I like to use it so I can 'Pattern: Fake it' in every green step.
Really the quickest and dirtiest I can make it.
Preferably returning a hardcoded value.
That makes my test green, so I know the test works.
Then I can refactor it to a more 'Pattern: obvious implementation'
So it's kinda a merge between
- Pattern: Fake it
- Pattern: obvious implementation
but split over green and refactor step.
But apparently I tend to over-engineer / un-tested-add functionality.
Which is not supposed to happen...
I want to read Book: Test Driven Development: By Example - Kent Beck again.
That is where I got these patterns from
After reading Book: Test Driven Development: By Example - Kent Beck again, I realized that I mis-interpreted 'Pattern: Fake it'
Aparently it means what I thought of as "remove duplication as a way to avoid triangulation".
It's called 'Pattern: Fake it (til you make it)'
And the 'til you make it' part is where the "remove duplication" happens to make the hacked hard-coded value into something more and more reasonable, iteratively.
After thinking some more, I may have discovered something where Object Calisthenics alone is insufficient.
TDD calls this "remove duplication"
Model: 4 rules of simple design calls this "has fewer elements"
Object Calisthenics has a force that drives for small things.
But that force can drive a lot of duplicate small things.
So after refactoring for the 'fewest elements', or 'remove duplication', I feel much better.
I could remove the duplication of ExpensePair and ExpenseTriple towards a ExpensesCollection which covered both cases.
So remove duplication driving a more generic solution.
I did notice a danger of falling into 'make everything more generic' mindset.
The rule here is "only make more generic, when it removes duplication".
And Model: 4 rules of simple design has an order of importance to the rules.
I did feel the trade-off of 'remove duplication' vs 'reveal intent'.
getAllTriples() {return getAllPermutations(getAllPairs());}getAllPairs() {return getAllPermutations(getAllSingles());}
I refactored to
getAllTriples() {return getAllPermutations(3);}getAllPairs() {return getAllPermutations(2);}And then considered inlining everywhere.This would reduce duplication, but also reduce clarity of intent.So I did not inline it.And after the 'clarity before non-duplication' mindset took hold, I also undid the previous refactor.