If the goal in writing software is to reduce it to a set of pieces that plug together, I'd agree. But it's an arbitrary metric and not an absolute measure of quality, not by a long shot.
Note that I don't say "composable", because composability is something that needs to be designed in, and it isn't usually clear how to do it best until the third or so time around - the rule of threes.
Furthermore, I don't say reusable, because reusability is something that also needs to be designed for. In particular, reusability in such a way that software can evolve over time without breaking clients (reusers) of the abstraction demands a tight and constrained contract that is broadened carefully, while testable components demand broad and flexible contracts, otherwise not everything would be available to be tested.
Every part that testing has forced you to break out to be individually addressable is a part that you cannot remove in a refactoring that significantly changes the way a library solves its problem.
A library that has been broken into parts that are neither composable nor reusable is simply over-complex. Almost every extant Java library is like this!
Of course, if you just write end software in small teams, none of this is relevant to you. But it is crucial in library design, especially when client code is outside of your organization.
But if instead of a monolith, you have a set of components with well defined interfaces that have simple semantics (that do not leak abstractions) -- whether or not these parts are re-usable in other contexts -- then you almost automatically have higher quality software:
* Easier to test means it will likely be better tested
* Well-defined interface and abstraction and a small implementation means that reviewing/correctness becomes easy. You only need to understand a small component to review it. "Obviously no bugs" rather than "no obvious bugs"
* Easier to split the work across developers
* Easier to comprehend the whole as a collection of its parts
The total number of lines of code, or even the total complexity may increase relatively to a monolithic design. But correctness becomes so much easier.
You mention refactoring, too, and IME, refactoring can be both easier or more difficult, depending on whether it is within a single small component or across multiple ones.
If you add architectural/design changes -- then it is night and day. A monolith will likely have to be rewritten to make an architectural change reliably.
A set of components can easily be split, for example, so that a few components are moved to run on a different system with a network protocol between them.
As for reuse, I like this quote: "Don't aim for reuse. Write small, independent components you can reason about, and the right pieces for reuse will fall out." Jessica Kerr @jessitron on Twitter
I don't think anyone disagrees that a "well designed and written" program of loosely coupled pieces has advantages over a monolithic one.[1] The debate is really over which is easier to do. And, the argument you are responding to is essentially, that it is easier to abstract out parts after you have done it in whole a few times. I know, personally, that that is a very compelling argument.
[1] Well, there probably is some debate on the feasibility of making things as loosely coupled as you would like. Back to the kernel debate, how many microkernels have survived with the device support of linux?
Software can easily consist of loosely coupled pieces as source code and be compiled and run as a single monolith with hardly any performance loss at runtime (versus coupled source code).
That is to say, that there is appeal to the "loosely coupled" dream of a software solution. Not just in source but in execution. However, there is the reality that this is very hard. The contention in this thread is that to think you can start in the loosely coupled set of parts is very ambitious.[1] It isn't that it is a bad goal. Just that it is akin to wanting to score well in a marathon without first running a few smaller races.
[1] Unless I am misrepresenting that, of course.
Trying to use the kernel as a template to guide all software development is not a great idea.
I have yet to see a prescriptive approach to this that works. About the best I've seen is the holistic iterative approach. First make something, then look to see where you can isolate changes and make them. Repeat. If this fits a model of TDD, it is new to me.
Programming speed is not the only consideration when it comes to shipping software. Squishing something together as rapidly as possible may shorten the programming time (and then only for smaller systems), but only at the cost of shoving the time into all the other phases, usually at a ratio greatly in excess of 1:1!
In other news, programmers are generally pretty bad as estimation, and this is probably related. I suspect the estimations for the "squeeze something together" part are pretty good overall, it's the rest that breaks down.
And again, to be clear, I'm not disagreeing that it's challenging. I'm saying that rather than being fundamentally challenging in a way that can never be made easier, it is a skill that can be learned. That makes for a very different cost/benefit set than a task that is fundamentally difficult. And, frankly, few developers are taking the time to learn it; far more are sneeringly dismissive at the skills that are required to learn this. Rather a shocking amount of our "structure" in programming is still just covering over cowboy programming with terms management can get behind. I think XP actually avoided this, but the average bastardization of XP is a thin patina of words over cowboy programming.
Now the major trick here is that this breaks down in categories that are effectively already solved. Which is why many of the examples are obnoxious to the point of unhelpful. If you know how to break something down to where it is loosely coupled parts already, I feel you should definitely do so.
Linux is well designed and you can learn from it, but in order to get value from that study you need to be a skilled C programmer and at the top of your game. Therefore it is a bad example for people who mainly use other languages.
In additon lets not forget that the SOLID principles, DRY, YAGNI and so on, are not hard and fast rules. Every extreme programmer will regularly violate those principles. The purpose of the principles is to guide your work, to make you see clearly what you are doing, so that when you violate a principle you do it for a good reason.
[1] Consider also this lovely thread: http://www.realworldtech.com/forum/?threadid=65915&curpostid...
You can do simple
things easily - and in particular, you can do things where
the information only passes in one direction quite easily,
but anythign else is much much harder, because there is
no "shared state" (by design). And in the absense of shared
state, you have a hell of a lot of problems trying to make
any decision that spans more than one entity in the
system.In the Linux kernel, you use printk to print messages on the console. That is modularity. There are device drivers. That is modularity. There is a range of loadable/configurable kernel modules for many things and these used to be more visible when more people would configure and build customized kernels. The Linux kernel has far more of these modules than earlier OSes that I used (TI DX10, UNIX 6th ed., Xenix, 3B2 Unix, SCO UNIX). The Linux kernel is linked into a monolithic binary that runs in kernel mode, but it is composed of many modules, some of which are integrated at link time and some of which are loaded dynamically (lsmod).
There is a good reason why the kernel is more monolithic than a business app, and that is that the kernel is doing a vastly different job at a vastly different layer of abstraction than a business app. You might also note that there are still lots of jobs for C programmers but most of them mention "embedded systems". That's what the Linux kernel is, a big featureful embedded system.
Perhaps some day someone will write a book on integration and cover all the different ways in which functionality can be integrated to produce an application. Most developers lean far too much on only one way of doing it, i.e. the link editor. For most apps, loosely coupled integration techniques are more valuable.
Consider, you can have a device driver that runs fine "on its own" but crashes when run with another driver loaded. This is almost canonically the opposite of loosely coupled modules.