I believe this is nothing but a matter of style - no "measure of readability" (which is always personal) can help you here.
So before I go any further, let me say this: Whatever the style choice, if you're working in a team, define a clear standard that the whole team must comply with, because if there is one thing that everyone can agree on that makes code less readable, is having to keep up with different style choices as you read it.
But now to the main subject: self
, this
or me
, as present in many OO languages, are featured roughly for the following reasons:
- Being able to pass an instance of the current object to others;
- Accessing special properties that are otherwise unavailable, such as reflective property
this.class
in Java;
- To allow otherwise conflicting names to be used in local scopes.
As with many other details that are abstracted away from us when we use OO languages (vtables, etc...), self
could also be hidden, if it weren't for the reasons above.
So my opinion is that you should only use it when necessary, as above.
Of course, this idea only works well along with the practice of making small classes and methods; otherwise, both your local and global scopes are going to be so big that it will always be confusing, forcing the reader to jump up and down in the code, looking for the definitions. But I'm tempted to say, in these cases, that it is not the consistency of style that is going to save you.
Beware of temporal coupling. However, this is not always an issue.
If you must perform steps in order, it follows that step 1 produces some object required for step 2 (e.g. a file stream or other data structure). This alone requires that the second function must be called after the first, it is not even possible to call them in the wrong order accidentally.
By splitting your functionality up into bite-sized pieces, each part is easier to understand, and definitely easier to test in isolation. If you have a huge 100 line function and something in the middle breaks, how does your failed test tell you what is wrong? If one of your five line methods breaks, your failed unit test directs you immediately toward the one piece of code that needs attention.
This is how complex code should look:
public List<Widget> process(File file) throws IOException {
try (BufferedReader in = new BufferedReader(new FileReader(file))) {
List<Widget> widgets = new LinkedList<>();
String line;
while ((line = in.readLine()) != null) {
if (isApplicable(line)) { // Filter blank lines, comments, etc.
Ore o = preprocess(line);
Ingot i = smelt(o);
Alloy a = combine(i, new Nonmetal('C'));
Widget w = smith(a);
widgets.add(w);
}
}
return widgets;
}
}
At any point during the process of converting raw data into a finished widget, each function returns something required by the next step in the process. One cannot form an alloy from slag, one must smelt (purify) it first. One may not create a widget without the proper allow (e.g. steel) as input.
The specific details of each step are contained in individual functions that can be tested: rather than unit testing the entire process of mining rocks and creating widgets, test each specific step. Now you have an easy way of ensuring that if your "create widget" process fails, you can narrow down the specific reason.
Aside from the benefits of testing and proving correctness, writing code this way is far easier to read. Nobody can understand a huge parameter list. Break it down into small pieces, and show what each little piece means: that is grokkable.
Best Answer
No, this is not a bad style. In fact it is a very good style.
Private functions need not exist simply because of reusability. That is certainly one good reason to create them, but there is another: decomposition.
Consider a function that does too much. It is a hundred lines long, and impossible to reason about.
If you split this function into smaller pieces, it still "does" as much work as before, but in smaller pieces. It calls other functions which should have descriptive names. The main function reads almost like a book: do A, then do B, then do C, etc. The functions it calls may only be called in one place, but now they are smaller. Any particular function is necessarily sandboxed from the other functions: they have different scopes.
When you decompose a large problem into smaller problems, even if those smaller problems (functions) are only used/solved once, you gain several benefits:
Readability. Nobody can read a monolithic function and understand what it does completely. You can either keep lying to yourself, or split it up into bite-sized chunks that make sense.
Locality of reference. It now becomes impossible to declare and use a variable, then have it stick around, and used again 100 lines later. These functions have different scopes.
Testing. While it is only necessary to unit test the public members of a class, it may be desirable to test certain private members as well. If there is a critical section of a long function that might benefit from testing, it is impossible to test it independently without extracting it to a separate function.
Modularity. Now that you have private functions, you may find one or more of them that could be extracted into a separate class, whether it is used only here or is reusable. To the previous point, this separate class is likely to be easier to test as well, since it will need a public interface.
The idea of splitting big code into smaller pieces that are easier to understand and test is a key point of Uncle Bob's book Clean Code. At the time of writing this answer the book is nine years old, but is just as relevant today as it was back then.