If you had to resubmit your code patch four times before you reached version number five, you'd quickly become a persona non grata on your team. Doubly so if every iteration of code review takes a day and it takes you a week to make a trivial 5-line change.
I wrote something on this idea as well referring to it as Debug Driven Development[0].
Similar to TDD or Domain Driven Design (DDD) we should aim to write code that is easy to debug. Store intermediate values in a pipeline in variables, store predicates in a variable with a human readable name, and avoid in lining complex function calls.
There are other techniques that serve this purpose and additional reasons than what this article explores of how or why this is helpful.
It's something I regularly say to our juniors - if you have two ways to do something and one is much easier to debug later, it's probably a better choice. It's a nice extension of not writing unnecessarily overcomplicated code and keeping things readable.
Past Barry writes comments to Future Barry, so Future Barry knows what the fuck is going on and doesn't have to try and figure out the spaghetti-code Past Barry apishly bludgeoned into the keyboard with his penis-tipped meat-stumps.
Both Barry's think the other is an insufferable asshole.
Kernighan's lever: Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it.
From a slightly different perspective, and not entirely disagreeing with you but moving into a related discussion: as an industry we sometimes forget that we can build higher level debuggers just like we build any other higher-level abstractions. If the question is "how do we insert a console.log here?" (or a logging.log() to a file on disk. Or RS232 null modem command sequence plus log ASCII. Depending on the app and era) it's sometimes fine to say that question is too low level on specific output format and that we should be reaching for higher-level debuggers.
I understand the problems that higher level debuggers take time for someone to build, install, or at least train on using, and that's often time that projects can't afford, but there can be huge benefits to Developer Experience if you build or find debuggers specific to your abstractions, especially in places like functional programming pipelines.
(As some easy examples, I've worked with the Redux Dev Tools a few times and getting a "redux-eye view" of an application's data state is sometimes incredibly more useful than trying to add a bunch of console.logs into all your actions and reducers manually. Similarly, I've become a big proponent lately of using RxJS-Spy for RxJS work: it adds a single operator `tag('pipeline-name')` that is a no-op in Production builds and then gives you a great Dev-time API for observing [or debugger breaking] specific pipelines or pipelines filtered by regex of name, all without manually adding or removing `tap` and `console.log` in those same pipelines.)
We don't always have to stick to lowest common denominator debug tools. We don't still need to make sure that we can pipe to our dev terminal over serial port to get our debugging done or log everything to plaintext files that are hard to manage. I think sometimes people forget that.
Similarly, I do find a responsibility that if I write code that is best debugged with higher level tools, that I make sure to document those tools, and try my best to train people on them as they ask.
When you program on a team choices like using procedural vs OOP vs FP aren't up to you since they impact others. You should be coding for your maintenance programmer, the person who will come behind you, need to quickly understand the code and make changes or bug fixes. The maintenance coder may be you, it might be a team mate or it might be someone yet to join the company.
Use your own time or projects foe exploring programming styles.
That's what this article is discussing: A means of writing code in an FP style that is easy for others less or unfamiliar with FP to maintain, debug, and refactor.
Functional programming if done well is much better for maintenance. I find a strictly typed functional code base very easy to maintain. It's far less a house of card.
Having worked in Perl for years I agree that Perl is most often hard to read. I've also seen some horrific FP. Generally that's because it either FP stuffed into a language that doesn't support it or it's some crazy meta programming that you need a PHD to follow. Boring well written FP is much easier to read IMO than most OO.
One of the nontechnical advantages of FP focused languages is that writing FP style code won't frighten or confuse anyone by itself. (But of course team maintained FP code should still be held to standards about clarity, maintainability, excess cleverness etc)
I don't think Functional Programmers have a fear of for-loops, I think they love for-loops, they just prefer to define them "steps first".
A silly cross-paradigm way to look at it is that a lot of functional programming is about "dependency injecting the for-loop" and "inversion of control of the for-loop". For similar reasons to dependency injection in OOP, functional programming wants to focus on the more interesting to the program abstractions up front and leave the final "for-loop wiring" to libraries or the language itself.
The for-loops don't go away, they are still there as necessary wiring. The functional programmer just wants to "dependency inject" all the interesting piece-at-a-time steps inside that for-loop rather than worry about the entire final wired loop body. Just like dependency injection in OO is supposed to make it possible to deal with a class at a time without needing as much of the entire class diagram in your head all once. Also, just like dependency injection: it's designed so that you have a lot smaller, easier to test pieces that make up the whole.
"Fear" could be a wrong word here. I think there's contempt involved instead. Which isn't justified either though. Everything is a tool, and should be used in a right context. I.e. for loops can be better justified sometimes (I find them useful around 1% of cases, but when they are they do their job well)
First we have to agree on what FP style even means.
For some it means chaining collection operations so "one line of code" solves an entire problem. This is not unlike the regexp wizard who can perform miracles with a regular expression which is incomprehensible to most other developers.
The above is what turns many people off from FP. As another commenter said, "what's wrong with a loop"? Indeed, accessibility matters; and if your team and new arrivals are not in that mindset, it's like a new language; and they already know a "language" and believe they are getting work done. Why complicate things?
I don't believe real FP is about doing these super concise expressions which can take considerable time to comprehend. I think FP is really more about pushing effects and mutations to edges, purifying (and simplifying) intermediate steps/functions, and using simple data structures.
The huge selling point of this kind of FP is in testability. When 90% of your code is pure and operates on simple data structures, testing each function is a breeze. No more do you need layers of setup and mocking. You don't even need full objects. If you're testing for a valid phone number in a person "record", you need only pass in a person: { phone: '12343241' }. All the rest of the crap which you would normally have to deal with, just to test this one thing, evaporates.
It does mean you end up with a LOT more functions. Then code organization and naming becomes a more significant challenge. But the end result, I believe, is still very much worth it.
And from my two recent experiences bringing this approach to OOP teams, I have found that after a brief period of fear and uncertainty, they end up adopting it enthusiastically.
> I think FP is really more about pushing effects and mutations to edges, purifying (and simplifying) intermediate steps/functions, and using simple data structures.
This 100%. Simple data structures with minimal use of side effects can often be used within in an OO code base without raising any eyebrows.
26 comments
[ 3.6 ms ] story [ 70.7 ms ] threadSimilar to TDD or Domain Driven Design (DDD) we should aim to write code that is easy to debug. Store intermediate values in a pipeline in variables, store predicates in a variable with a human readable name, and avoid in lining complex function calls.
There are other techniques that serve this purpose and additional reasons than what this article explores of how or why this is helpful.
[0] https://sambernheim.com/blog/debug-driven-development
It's something I regularly say to our juniors - if you have two ways to do something and one is much easier to debug later, it's probably a better choice. It's a nice extension of not writing unnecessarily overcomplicated code and keeping things readable.
I have to remind myself of this concept often as it’s just so easy to do things quick and dirty
Both Barry's think the other is an insufferable asshole.
I understand the problems that higher level debuggers take time for someone to build, install, or at least train on using, and that's often time that projects can't afford, but there can be huge benefits to Developer Experience if you build or find debuggers specific to your abstractions, especially in places like functional programming pipelines.
(As some easy examples, I've worked with the Redux Dev Tools a few times and getting a "redux-eye view" of an application's data state is sometimes incredibly more useful than trying to add a bunch of console.logs into all your actions and reducers manually. Similarly, I've become a big proponent lately of using RxJS-Spy for RxJS work: it adds a single operator `tag('pipeline-name')` that is a no-op in Production builds and then gives you a great Dev-time API for observing [or debugger breaking] specific pipelines or pipelines filtered by regex of name, all without manually adding or removing `tap` and `console.log` in those same pipelines.)
We don't always have to stick to lowest common denominator debug tools. We don't still need to make sure that we can pipe to our dev terminal over serial port to get our debugging done or log everything to plaintext files that are hard to manage. I think sometimes people forget that.
Similarly, I do find a responsibility that if I write code that is best debugged with higher level tools, that I make sure to document those tools, and try my best to train people on them as they ask.
Use your own time or projects foe exploring programming styles.
A silly cross-paradigm way to look at it is that a lot of functional programming is about "dependency injecting the for-loop" and "inversion of control of the for-loop". For similar reasons to dependency injection in OOP, functional programming wants to focus on the more interesting to the program abstractions up front and leave the final "for-loop wiring" to libraries or the language itself.
The for-loops don't go away, they are still there as necessary wiring. The functional programmer just wants to "dependency inject" all the interesting piece-at-a-time steps inside that for-loop rather than worry about the entire final wired loop body. Just like dependency injection in OO is supposed to make it possible to deal with a class at a time without needing as much of the entire class diagram in your head all once. Also, just like dependency injection: it's designed so that you have a lot smaller, easier to test pieces that make up the whole.
This is valid criticism in many cases. Code has two interfaces - the compiler and humans.
For some it means chaining collection operations so "one line of code" solves an entire problem. This is not unlike the regexp wizard who can perform miracles with a regular expression which is incomprehensible to most other developers.
The above is what turns many people off from FP. As another commenter said, "what's wrong with a loop"? Indeed, accessibility matters; and if your team and new arrivals are not in that mindset, it's like a new language; and they already know a "language" and believe they are getting work done. Why complicate things?
I don't believe real FP is about doing these super concise expressions which can take considerable time to comprehend. I think FP is really more about pushing effects and mutations to edges, purifying (and simplifying) intermediate steps/functions, and using simple data structures.
The huge selling point of this kind of FP is in testability. When 90% of your code is pure and operates on simple data structures, testing each function is a breeze. No more do you need layers of setup and mocking. You don't even need full objects. If you're testing for a valid phone number in a person "record", you need only pass in a person: { phone: '12343241' }. All the rest of the crap which you would normally have to deal with, just to test this one thing, evaporates.
It does mean you end up with a LOT more functions. Then code organization and naming becomes a more significant challenge. But the end result, I believe, is still very much worth it.
And from my two recent experiences bringing this approach to OOP teams, I have found that after a brief period of fear and uncertainty, they end up adopting it enthusiastically.
This 100%. Simple data structures with minimal use of side effects can often be used within in an OO code base without raising any eyebrows.
You've just described the Rust programming language.
May the Borrow Checker bless you and all your bits until the head-death of the universe.
https://www.reddit.com/r/DilbertProgramming/comments/qg99f0/...