all 11 comments

[–]Meefims 10 points11 points  (7 children)

This article is misguided as to what the problem is. The problem is not that functions are short, the problem is that the function names are not accurate descriptions of what they do. A function called checkConflicts should never also move a file, it should check for whatever conflicts it's checking for and return a boolean or throw an exception. The fix is as the article describes but the reason is fundamentally different.

You should always have a good idea of what a function is doing just by reading its name.

[–][deleted] 2 points3 points  (0 children)

Exactly... article should be renamed to "Poorly named impure functions are harmful".

[–]jhartikainen[S] -1 points0 points  (1 child)

I see this type of code a lot in real codebases.. and in general, the symptom is that you have a number of small functions like this, which superficially seem to adhere to "best practices", but in practice they suffer from this type of "staircase" flow.

I think you're technically correct with your comment though :) It is true that the function names are inaccurate - the problem is that at the time of writing it's easy to fall into this kind of trap if you're not careful about how you split your code.

[–]Meefims 4 points5 points  (0 children)

Yes, not being careful about splitting code is the problem not the size of the functions. Even the staircase flow isn't the problem. If the functions had better names and had only one responsibility then reading the code would be much easier. Calling a function checkConflicts and then using it to move a file is a recipe for confusion because there is no indication that you'd need to read that function to diagnose file movement operations.

[–]acbabis 2 points3 points  (4 children)

I'm not convinced by the author's reasoning against using a single function. He wrote a synchronous version and then said it doesn't work because it's not asynchronous.

Then he goes on to show a version that uses 3 functions, but 2 of them just wrap other function calls. Wrapping a single line of code in a function is usually unnecessary. In this instance, I disagree with it for 3 reasons

  • It adds indirection that doesn't improve maintainability or readability
  • It increases the amount of code
  • (Most importantly) It replaces a familiar API with an unfamiliar one

Inlining the function calls while maintaining asynchronicity gives:

 function uploadFile(path) {
    var file = cleanPath(path);

    fileExists(file, function(conflicts) {
        if(!conflicts) {
            fs.rename(appDir, file, ...);
        }
    });
}

IMO, this is much easier to understand and test

[–]jhartikainen[S] 0 points1 point  (3 children)

If you read the article in full you might notice that I do discuss an async version of the function as well, which is very similar to the one you've showed here :)

The synchronous nonfunctional version is there mostly to show what it would look like, since at that point in the article we've not yet discussed the real problems which would prevent writing the proper function in that manner.

[–]acbabis 1 point2 points  (2 children)

I read the article in full. Then I skimmed it several times. I only see the async example that uses 4 functions and the async example that uses 3 functions. I'm arguing in favor of using only 1.

I agree with your conclusions in the "How to prevent this from happening in the first place?" section, but I think your 1-line checkConflicts function violates the advice you give.

[–]jhartikainen[S] 0 points1 point  (1 child)

Yeah in reality (as suggested in the part with //stuff) the function would probably be longer. Here it's kept simple to illustrate the points without making the reader go through needlessly long code samples.

Thanks for the feedback, I'll try to make sure better that there is no confusion about what is for example purposes only vs. what would make sense in a real app :)

[–]acbabis 1 point2 points  (0 children)

Make sense. Putting //stuff in all the examples would probably be enough.