14 Comments
May 16, 2023Liked by Kent Beck

"if my gut tells"...

I think the designer decision for concrete or abstract is proportional to the knowledge of the domain. When you know the domain you know what moves/changes frequently, what is fixed, what has many scenarios, etc and it's easier to find the right balance.

In a scenario where you have a low domain knowledge it is common to make bad decisions on the tradeoff curve, for both sides. In this context I would suggest to got for more concretes design since it is easier to refactoring and avoid premature optimisation (in this case, predicting scenarios that you don't know for sure)

Expand full comment

I'm sure many of you won't be able to see this, as it's in Korean, but I'd like to share the following link I wrote that was inspired by this article.

https://yozm.wishket.com/magazine/detail/2068/

Expand full comment
founding
May 17, 2023Liked by Kent Beck

Recently, I came across advice suggesting we should "Strive for unchanging tests" in the book 'Engineering at Google'. This resonated with your discussion about controllability. I believe the aim of creating code that is resilient to change, or can be easily modified when necessary, is not exclusive to tests but extends to any piece of code we produce.

In your view, how closely does this idea of striving for unchanging code align with the controllability you're discussing? Do you see them as two facets of the same principle?

Expand full comment
May 16, 2023·edited May 16, 2023Liked by Kent Beck

I could still say both examples, the socket connection, and the "customer" are the same.

For the "outside" world, what is important is "where you communicate with", "whom you are talking about".

The fact that your implementation needs a "socket" object from IP/PORT, or a processed version of your customer is the same.

What you change is what is taken as argument to the function you test.

You could still have your

```

main_function(ip, port)

main_function(customer)

```

But instead of deeply test `main_function` directly, you test `subfunction` that works on easier to test parameters.

You can still have one test for `main_function` but once you tested the socket connection creation/the customer important things extractor, no need to test all the combinations on this one.

The question is, can the outside world cope with your better to test interface.

Being able to move that interface you want to run your tests against to the main function, or just leave it to sub-functions.

Some patterns may simplify this, like typical factory pattern where users would only create their object with `from_customer`, `from_ip_port`, and only your tests would call it without the factory (except once to test the factory). But its in practice the same as "main_function_wrapper(ip, port)" -> "subfunction(connection)" where you would only test subfunction.

I often rely on switching from operation on an object/having external side-effects, to testing a pure subfunction only working on stdlib objects where I can really easily spam tests (even abusing python `doctest` to test all in the docstring).

And the "side-effect/object specific" unwrangling just needs one test to show it handles modifying a real object.

Expand full comment

I like this kind of specific advice. I wonder where I can read more of your content on software design. Is tidy first? book coming up the best source, or prior blog posts or books you’ve written may have more of this low level content? I enjoy higher level concepts too, but I’m particularly interested in how to write better code.

Expand full comment

Isn't this where the idea of interface segregation can be applied? The customer object has too much information given what is required for the mortality lookup. If the mortality table took a MortalityLookupSpec which provided the necessary bits then the test can just pass that, and changes to the spec are not too painful. Customer could even implement the interface which may work from a system perspective but would seem a little weird.

Then the solution becomes about the most appropriate abstraction rather than abstract v concrete

Expand full comment

Would tools like Mockito get rid of the motivation to go for more concrete parameters, except for objects that violate Heuristic of Demeter?

(1) With Mockito, could we solve the first case without going for more concrete parameters?

```

class MortalityTableTest {

@Test retrieveMaleNonSmoker() {

// Arrange

// pseudo-code, don't remember the syntax

Customer customer = new Mock<Customer>();

when(customer.getGender()).thenReturn(Gender.Male);

when(customer.getSmoker()).thenReturn(Smoker.NO);

// Act

MortalityTable result= MortalityTable.lookup(customer);

// Assert

assertEquals("QM115", result.getName());

}

}

```

(2) If Customer violates Heuristic of Demeter, then yes, setting up the mock would be complicated, and there would be motivation to go for concrete parameters.

E.g., if retrieving gender was `customer.x().y().getGender()` or other complex logic.

Does the above make sense? ... And if yes, what motivations or examples would there be for going for concrete parameters for controllability outside of objects violating Demeter? (.. Perhaps there're cons to using the Mockito pattern in the first place?)

Expand full comment