📜 ⬆️ ⬇️

Still messing around with debugging?

Hello, dear readers.

In Russian there is not so much universal non-aging literature on the principles of the PLO. Taking this opportunity, we propose to download Matt Weisfeld’s " Object-Oriented Thinking ", which is almost no longer on paper. However, such books appear from time to time, and there are those that are written in an innovative and applied style, and not just grind known truths. One of them is called " Elegant Objects ", we are seriously thinking of publishing it in Russian.


')
We propose to evaluate the style and philosophy of the author to translate one of the latest articles from his blog.



Debugging is “the process of interactively launching a program / method, and the execution flow after each instruction is suspended and the result is displayed ...”. In essence, this is a very efficient technique ... for a bad programmer. Or for an old school programmer who still writes a procedural code in C. OOP specialists do not debug the code — they write unit tests. I would like to say that unit testing will completely remove debugging from the agenda. If debugging is required, then the program is designed poorly .



Suppose I am a bad programmer, I write in an imperative procedural style, and issue the following Java code:

class FileUtils { public static Iterable<String> readWords(File f) { String text = new String( Files.readAllBytes(Paths.get(f)), "UTF-8" ); Set<String> words = new HashSet<>(); for (String word : text.split(" ")) { words.add(word); } return words; } } 


This static helper method reads the contents of the file, and then finds all unique words in it. It's simple. However, if he refuses to work, what will we do? Suppose here is a file:

 We know what we are, but know not what we may be. 


From it we extract the following list of words:

 "We" "know" "what" "we" "are,\n" "but" "not" "may" "be\n" 


In this case, I do not like ... just the next step - which one? Either the file is read with an error, or an error in the breaking of lines. Let's debug it , right? We run the file through the input and step by step we work it out, tracking all the variables and watching them. We find a bug, fixed. But if a similar problem occurs again, it will have to be debugged again! These are the cases that should be prevented with unit tests.

We need to write a unit test once and thereby reproduce the problem. Then we fix the problem and make sure the test passes. Thus, problems are solved rationally. We will not have to fix anything again, since the error will no longer occur. Our test is guaranteed.

However, all this works only if writing a unit test is easy. If it will be difficult to write it, I will become ashamed and will not write. Just do the debugging and fix the problem. In this particular case, writing a test is not easy. That is, the level of complexity of the test is quite high. It is required to create a temporary file, fill it with data, run the method and check the result. To understand what is happening, and where is the bug, you will need more than one test. To avoid duplicate code, I also need to write a few utilities that will help me create a temporary file and fill it with data. A lot of work. Maybe “not too much”, but with debugging I would manage in a few minutes.

Accordingly, if it seems to you that debugging is too complex and stalled, think about the quality of your code. I bet there will be plenty of refactoring options for it, just like for the code from the listing above. This is how I would change it. First, we redo it into a class, because auxiliary static methods are flawed :

 class Words implements Iterable<String> { private final File file; Words(File src) { this.file = src; } @Override public Iterator<String> iterator() { String text = new String( Files.readAllBytes(Paths.get(this.file)), "UTF-8" ); Set<String> words = new HashSet<>(); for (String word : words.split(" ")) { words.add(word); } return words.iterator(); } } 


Already much better, but still difficult. Next, break it into smaller classes:

 class Text { private final File file; Text(File src) { this.file = src; } @Override public String toString() { return new String( Files.readAllBytes(Paths.get(this.file)), "UTF-8" ); } } class Words implements Iterable<String> { private final String text; Words(String txt) { this.text = txt; } @Override public Iterator<String> iterator() { Set<String> words = new HashSet<>(); for (String word : this.text.split(" ")) { words.add(word); } return words.iterator(); } } 


Well, how? Write a test for the Words
class Words
- a completely trivial task:

 import org.junit.Test; import static org.hamcrest.MatcherAssert.*; import static org.hamcrest.Matchers.*; public class WordsTest { @Test public void parsesSimpleText() { assertThat( new Words("How are you?"), hasItems("How", "are", "you") ); } } 


How much time did I spend on it? Less than a minute. You do not have to create a temporary file, fill it with data, because the Words class does not work with files. It simply parses the incoming line and finds unique words in it. Now it is easy to fix the error, because the test is small, and we will not be difficult to write other tests, for example:

 import org.junit.Test; import static org.hamcrest.MatcherAssert.*; import static org.hamcrest.Matchers.*; public class WordsTest { @Test public void parsesSimpleText() { assertThat( new Words("How are you?"), hasItems("How", "are", "you") ); } @Test public void parsesMultipleLines() { assertThat( new Words("first line\nsecond line\n"), hasItems("first", "second", "line") ); } } 


I believe that debugging is necessary when it takes significantly more time to write a unit test than to press the Trace-In / Trace-Out buttons. This is logical. We are all lazy, we love simple and quick solutions. But debugging is a waste of time and effort. It only helps to find problems, but not to insure against their reappearance.

Debugging is needed in procedural and algorithmic code, when the code describes how the goal is achieved, and not what the goal is. Reconsider the above examples. The entire first static method talks about how to read a file, parse it, and find words. It is even called readWords() (“read” is a verb ). On the contrary, the second example indicates which goal should be achieved. Speech is either about the text of the Text file or about the words Words in the text (these are nouns ).

I believe that in the right OOP, debugging is inappropriate. Only unit testing!

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


All Articles