📜 ⬆️ ⬇️

Thorns around gold

Author's Note: This is a translation of the article by Bob Martin
I was inspired to write this article by the article by Mark Siman “The IsNullOrWhiteSpace trap” (@ploeh). Mark's article is brief and well-written. Please read it first before continuing to read this.
The trap Mark is talking about is a special case of a more general trap, which I call stealing gold . I can demonstrate this trap by going back to Mark’s article.

Notice that the first test Mark wrote was as follows:

[InlineData("Seven Lions Polarized" , "LIONS POLARIZED SEVEN" )] [InlineData("seven lions polarized" , "LIONS POLARIZED SEVEN" )] [InlineData("Polarized seven lions" , "LIONS POLARIZED SEVEN" )] [InlineData("Au5 Crystal Mathematics", "AU5 CRYSTAL MATHEMATICS")] [InlineData("crystal mathematics au5", "AU5 CRYSTAL MATHEMATICS")] 

He already fell into the trap. Why? Because he already stole the gold .

Gold and thorns


The main functionality Mark is trying to describe is the alphabetical ordering of words. Naturally, his tests reflect this functionality. The main functionality is gold and he stole it.
The problem is that gold is protected by an invisible thorny hedge that entangles anyone, an unsuspecting programmer, who, being blinded by gold , will try to steal it. What a thorny hedge? In the case of Mark, this is null and an empty string as input.
I have been following the TDD discipline for fifteen years now. I learned a lot about this invisible thorny hedge. I learned a lesson that she is always somewhere near. I realized that if you try to steal gold too early, an invisible fence will prevent your progress and tear your efforts to pieces [1]. So, the strategy that I learned to follow is to turn my eyes away from gold for a while, while I feel the fence and clear the way from it.
')

Intelligence and Clearing


Before embarking on core functionality, I write as many tests as possible that ignore core functionality, and instead focus on exceptional, degenerative auxiliary behaviors. It is in this sequence. One by one, I write such tests and make them pass.

Exception behavior


These are behaviors that detect incorrect input that the core functionality should never encounter. Such behaviors return error codes, log errors and \ or throw exceptions.
In the case of Mark, handling null is the only exceptional behavior. But in more complex applications, the detection of exceptional cases can be much more difficult. Of course, these cases include input processing. But they also include breaking semantics, such as deleting a non-existing record, or adding a record that already exists.

Degenerative behaviors


Here we are talking about input data, which force the main functionality to do “nothing”. I put “nothing” in quotes, because sometimes “nothing” can be relatively complex.
In the case of Mark, blank lines and lines consisting of spaces are degenerative input. Ultimately, he solved the problem of such strings with a complex set of conditions and operations that returned an empty string in the case of one whitespace character or an empty string as input, in other cases all whitespace characters were deleted [2].
In general, degenerative conditions are things like spaces, blank lines, empty collections, zero-length arrays, etc. In more complex applications, the degenerative case can be quite complex and require complex processing. Consider, for example, a Java compiler that processes source files that contain thousands of lines, which consist of semicolons and comments. What should be the result of processing?

Helper behaviors


These cases are sometimes the hardest to find. Supporting behaviors are those that surround and support the core functionality, but are not part of it. For example, the getSize () function of the Stack class. The response to the size request is not related to the core functionality that implements LIFO.
The fact is that helper behaviors are often useful for basic functionality for obvious reasons. For example, it turns out that the stack size is an array index that is used for push and pull operations in fixed-size stacks. I usually encounter the fact that, after implementing all the supporting behaviors, the core functionality is much easier to implement.

I write all these tests first and make them pass. I avoid any tests that are close to the main functionality until I finally surround the problem with passing tests that describe everything except the main functionality. Then, and only then, I take the gold .

[1] Indeed, just the day before yesterday I spent four hours, I would be entangled in thorns that I missed and didn’t clear away properly. In the end, git reset - hard turned out to be my only way out.

[2] Did he cover all possible conditions? What about tabs, line breaks, backspaces, non-printable characters?

Source: https://habr.com/ru/post/254951/


All Articles