This is like a balancing question in my mind with no easy right and wrong answer. I'll just go with an approach of sharing my personal experiences here including my own tendencies and mistakes throughout my career. YMMV considerably.
As a caveat I work in areas that involve some very large-scale codebases (millions of LOC, sometimes decades-long legacy). I also work in a particularly peculiar area where no amount of commenting or code clarity can necessarily translate to any competent developer being able to understand what the implementation is doing (we cannot necessarily take any decent developer and get him to understand the implementation of a state-of-the-art fluid dynamics implementation based on a paper published 6 months ago without him spending a good amount of time away from the code in specializing in this area). This generally means that only a few developers tops can effectively understand and maintain any particular part of the codebase. Further it means that a lot of code can quickly become obsolete and could be due for deprecation and possibly wholesale replacements given how rapidly the computer graphics industry moves with yesterday's techniques becoming obsolete today.
Given my particular experiences and perhaps combined with the peculiar nature of this industry, I no longer found it productive to take SoC, DRY, making function implementations as readable as possible, even reusability to its utmost limits in favor of YAGNI, decoupling, testability, writing tests, interface documentation (so we at least know how to use an interface even if the implementation requires too much specialized knowledge) and ultimately shipping the software.
Lego Blocks
I was actually prone to go the total opposite direction originally at some point earlier in my career. I got excited so much by functional programming and policy class designs in Modern C++ Design and template metaprogramming and so forth. In particular I was excited by the most compact and orthogonal designs where you have all these little pieces of functionality (like "atoms") you can combine together (to form "molecules") in seemingly infinite ways to get desired results. It got me wanting to write almost everything as functions which consisted of a few lines of code, and there's not necessarily anything inherently wrong with such a short function (it can still be very wide in applicability and clarify code), except I was starting to go in the dogmatic direction of thinking my code had something wrong if any function spanned more than a few lines. And I got some really neat toys and even some production code out of that type of code but I was ignoring the clock: the hours and days and weeks rolling by.
In particular while I admired the simplicity of each little "lego block" I created that I could combine in infinite ways, I ignored the amount of time and brainpower I was putting into to piecing all these blocks together to form some elaborate "contraption". Moreover in the rare but painful instances where something went wrong with the elaborate contraption, I willfully ignored the time I was spending trying to figure out what went wrong tracing through a seemingly endless array of function calls analyzing each and every decentralized lego piece and subsets of their combinations when the whole thing might have been a lot simpler if it wasn't made out of these "legos", if you will, and just written as a handful of meatier functions or a medium-weight class. This might have also been exacerbated by how I was using tools and languages not necessarily intended to be used this way (I wasn't using actual functional languages to implement these ideas, e.g.), or maybe I just wasn't a good enough developer to make this all work out so well in a reasonable time (can accept that possibility).
Still I came around full circle and as deadlines forced me to become more conscious of time, I started to realize that my endeavors were teaching me more about what I was doing wrong than what I was doing right. I started to once again appreciate the meatier function and object/component here and there, that there are more pragmatic ways to achieve a reasonable degree of SoC as David Arno
points out by separating file input from string processing without necessarily decomposing the string processing down to the most granular level imaginable.
Meatier Functions
And even more so I started to be okay with even some code duplication, even some logical duplication (I'm not saying copy and paste coding, everything I'm talking about is finding "balance"), provided that the function is not prone to incur repeated changes and documented in terms of its usage and most of all well-tested to make sure its functionality matches up correctly with what it's documented to do and stays that way. I started to realize that reusability is largely tied to reliability.
I've come to realize that even the meatiest function which is still singular enough in concern to not be too narrowly applicable and too clunky to use and test, even if it duplicates some logic in some distant functions elsewhere in the codebase, and provided it's well-tested and reliable and the tests reasonably ensure it remains that way, is still preferable to the most decomposed and flexible combo of functions that lack this quality. So I've come to like some of the meatier stuff these days well enough if it's reliable.
It also seems to me that most of the time, it's cheaper to realize you Are gonna need something in hindsight and add it, provided your code is at least receptive to new additions without cascading hellfire, than to code all sorts of things when you aren't gonna need it and then face the temptation of removing it all when it's starting to become a real PITA to maintain.
So that's what I've learned, those are the lessons I've deemed most necessary for me to personally learn in hindsight in this context, and as a caveat it should be taken with a grain of salt. YMMV. But hopefully that might be of some value to you in helping you to find the right sort of balance to ship products that make your users happy within a reasonable amount of time and maintain them effectively.
line_number = 0
is a numeric default, andline_number = line
assigns a string value (which is the line contents not it's position)open
andin
. Re-implementing existing functions does not increase separation of concerns, the concern is already handled in the existing function!