The MicroPHP Fallacy
blog.ircmaxell.com
blog.ircmaxell.com
This way your toolbox is much lighter and easier to carry around (easier to keep up to date with improvements to the code and easier to maintain). I think it's about breaking things into smaller pieces.
Which is when I realised the utility of a bulky framework; for the most part it is a suite of libraries that work together/are compatible but which someone else maintains for me :)
This was a big step for me.
(although I still use the lightweight approach for smaller projects)
At which point it's basically a framework (perhaps without the routing/initialisation code).
One thing I would like to see (no idea if it exists) is a core package architecture (that did all of the above) and let you hang packages as and when you need them.
I've been keeping an eye out for such a thing for a while with no joy.
I don't know how relevant this is but I couldn't help but notice that the "hammer" analogy for frameworks was an interesting choice in light of Joel S.'s well known "Hammer Factory Factory" article. http://discuss.joelonsoftware.com/default.asp?joel.3.219431.... Was this a coincidence or intentional? Anyway I thought it was a funny coincidence. :)
When it was brought up in the context of this article, I read it as someone being proud to be a developer of PHP. Among many academic crowds and HN it feels like PHP is a dirty word.
By contrast, in method 2, the complexity is hidden. If there's a bug in method 2, I have way more levels of abstraction to dig through before I've got a clue what's going on. If someone else wrote method 2, I've got probably three or four source files to do archaeology in before I can even be sure what it does.
As far as "the complexity is hidden", you're absolutely right. I want that complexity hidden. By hiding the complexity in this way, I can reduce duplication and at the same time make the code far easier to read. Sure, you do need to dig through more levels of abstraction if you need to debug something. But abstracting in this way enables bugs to be fixed far easier, since the methods are really small and simple, the "ripple" effect is far easier to understand and contain.
Think about how long it took you to understand what log() did. With the first one, you needed to parse a whole lot of detail (including the flag passed to fopen, the two exception checks, the arguments passed to flock, the fprintf declaration and the flock call arguments). With the second, all you needed to do is read the two steps: 1. createLogMessage() and 2. file->append($message). At a glance you know what the method is supposed to be doing.
Why is there such a hang up that people want to know what code is doing at all levels? If you name your APIs well, you should be able to look at the method's name (and perhaps its arguments in some cases) and know without a doubt what it's doing (at least to the abstraction level the API is designed for). If you really need to know details, you can go deeper, but I know when I read $file->append($message) that I'm appending a message to a file. I don't need to worry about anything else 99.9% of the time. So I'd rather get the clean win with well named APIs, than spend my time sifting through methods like the first one...
So yes, absolutely hide complexity. But hide it judiciously; hide it only when you know you have a clean, well-designed and well-constructed abstraction that won't leak to the outside. But if you hide complexity behind a leaky abstraction, which most of them are; then now, as they say, you have two problems.
Except in the well known cases, like trying to abstract away SQL to give it an OO interface, the leakiness of abstractions is rarely a big issue if those abstractions are decently designed. Not even beautiful and perfectly clean, just good enough for their purpose.
Yeah, it sucks in those rare cases where the abstraction makes it hard to figure out what the hell is actually happening, but the extra effort is nothing compared to the amount of pain saved by having that abstraction throughout the rest of the development process.
All plumbing will spring a leak some time (and sometimes with pretty costly consequences), but that in itself is no argument against plumbing.
For reference, here's the first example:
public function log($message, $level) {
$f = fopen($this->logFile, 'a');
if (!$f) {
throw new LogicException('Could not open file for writing');
}
if (!flock($f, LOCK_EX | LOCK_NB)) {
throw new RuntimeException('Could not lock file');
}
fprintf($f, '%s [%s] %s - %s', $this->machineName, date('Y-m-d H:i:s'), $level, $message);
flock($f, LOCK_UN);
fclose($f);
}
And here's what your improved example would actually be: /* In $this->file's class */
public function append ($message) {
$f = fopen($this->logFile, 'a');
if (!$f) {
throw new LogicException('Could not open file for writing');
}
if (!flock($f, LOCK_EX | LOCK_NB)) {
throw new RuntimeException('Could not lock file');
}
fwrite($f, $message);
flock($f, LOCK_UN);
fclose($f);
}
/* In Log class */
public function createLogMessage ($message, $level) {
return sprintf('%s [%s] %s - %s', $this->machineName, date('Y-m-d H:i:s'), $level, $message);
}
public function log($message, $level = 'notice') {
$message = $this->createLogMessage($message, $level);
$this->file->append($message);
}
Just a nitpick, but sometimes we over-abstract because we're too used to doing so. To me, a separate `createLogMessage()` method that just wraps a `sprintf()` seems like a sign of that.(not saying I haven't been equally guilty of this 1000x before :)
I'd probably do something more like this:
public $format = '%s [%s] %s - %s'; // machine, date, level, message
public function formatMessage ($level, $message) {
return sprintf ($this->format, $this->machine, date('Y-m-d H:i:s'), $level, $message);
}
public function log ($level, $message) {
return $this->write ($this->formatMessage ($level, $message));
}
That way you could change the format with no subclassing needed: $logger->format = '%s, %s, %s, %s';
I also like the idea of using a stream wrapper to override the logFile property: $logger->logFile = 'db://user:pass@localhost';
So again instead of subclassing, you just implement a stream wrapper as shown here:http://www.php.net/manual/en/stream.streamwrapper.example-1....
Perhaps that's just my personal aversion to needless depth in class hierarchy talking though... :)
Edit: Wow does this thread have me geeking out! Haha, cheers :)
I do think that choosing where to abstract and where to split functionality is the "art" of the code and we obsess about as we master the craft. So I can appreciate your code example!
I personally like to have a dedicated log function. That way you can refactor from using a file to perhaps a database table or any other method of logging. I personally expose our error logs as RSS and subscribe to the feed - I find it a really easy way to monitor because I'm too lazy to go looking at error log files every day!
Perhaps such a core Log class could use a separate createLogMessage() method (or formatMessage() as I'd probably name it) that could be overridden in a subclass more easily than overriding the entire log() method.
I definitely agree that the API design and finding that balance is a big part of what makes coding an artform!
Learning how to program involves learning to think like a computer, and seeing the world in terms of loops and filters and manipulations. Mastering a specific programming language takes that one step further, because you become so familiar with it that you start thinking in terms of the specific constructs and the idiom that it offers. The speed gains are enormous once your brain adapts to that way of thinking, and I wouldn't give that up for anything.
I appreciate the sentiment – be a programmer, not the master of one specific way of programming – but I think there are better ways to achieve that goal than by programming "into" a language. The Pragmatic Programmer credo, for example, to learn a new language ever year, is much more appealing.
Once your application has been in circulation, exposed to the elements, hostile or indifferent users, it will develop barnacle-like patches that look ugly but serve a specific and important purpose.
You can keep code clean(er) if you're vigilant, but sometimes there's no way to express very complicated logic in a concise manner. Often abstraction looks cleaner but generally only hides complexity and can tend to increase complexity on the whole.
But there is all this weirdness validating data inputs like dates. And we have to process the form data data so each field input goes to the proper API field. And hey look that mapping only varies in its _data_ among form / API target pairings, so I should abstract that code out into a function or I'll be chasing bugs through the copied-and-pasted. The API provides a library for formulating requests but this doesn't expose some key functionalities to unit testing so I ant to revise to do that. Et cetera.
And, the next thing I know this thing is umpty-ump lines, and while the scripts actually processing the data are reasonably legible, they're sitting over a tangle of stuff that's rather harder to navigate. I've got classes to manipulate various core data to produce various data structures supporting sundry activities, but this essentially means those data structures are abstractions and thus hard to inspect when trying to remember how some process is working or what's going wrong. (The unit tests are worthwhile if only b/c their check values provide concrete examples of how those derived structures look.) To make the code "simple" in one place, I've created complexity elsewhere.
Obviously, this is in part a novice problem. If I can lay out and document the supporting code better everything gets easier to follow. But still, 5000 lines to input some form data, am I doing this right?
So is "simple, readable code" that helpful as a goal? At the end of the day it seems like there is X degree of complexity in any of these tasks, and any strategy to "simplify" is really just a framework for deciding where to place what complexities. And if that's right, then "simple, readable code" can become either a time suck or a discouragement in a hurry. I'm either playing Whack-A-Mole as I chase Complexity from one corner to the next and back to the start, or I'm going home depressed b/c I'm too stupid to write "simple, readable code". It sounds great, I suppose it is where people end up when they get good, so it's probably very helpful as an indicator of whether you're good yet. But I'm not sure it's very helpful for figuring out how to get to good.
Code Complete documents several best practices in coding based on internal reviews of large coding projects and publications. The point of the book is that your code will have fewer bugs and be easier to maintain.
Priorities for coding go something like this:
1. make it work
2. optimize for maintenance (ie, make it easy to read)
3. optimize for flexibility/speed (only if necessary!!!!)
You should not be chasing complexity from one corner to the next; complexity should nearly always be pushed down and sequestered where it can't infect other code. Simple, readable code is the primary goal. Perhaps it was Knuth? who said programmers should enjoy reading code on the weekend. I couldn't disagree more! Good code should have the opposite qualities of a good book. Every single step along the way should be perfectly obvious as if no other way existed; plot twists in code are bad! Elegance is good, while cleverness tends to be bad. Working on existing code is much harder than writing new code. What hope do you have with maintenance if you write something at the limits of your comprehension?
About 70-80% of the lifetime of any work on given code is actually in maintenance, not in writing it. Optimize for this. Documenting is not necessarily helpful, either. You should not document shit as being shit; instead, you should change the bad code!
Other things are mentioned too, like function length, number of arguments to functions, variable span length, and variable pass through. Really, give it a read.
thx
Why nitpick? There's always more to what you read than just the words on screen. Considering the motivation and circumstances behind the MicroPHP Manifesto I can't see why anyone would make it out to be something it isn't, bad analogies or not.
Ed is a personal friend (and we do a podcast together) with many, MANY years of experience building applications using PHP. While I don't agree with him on some points in there, he does deserve to not be shit on by people who can't be bothered to do 30 seconds of research about the author.
Now the author of this article refuting the Manifesto seems to be under the impression that what Ed suggests should be the way things are done from now on. It's clear as day that all Ed was sayng was that sometimes the MicroPHP Manifesto tenets make more sense. And they do! Sometimes!
Guess what? Everyone is right! Hopefully that doesn't make some people's brain explode but both authors are correct. Mileage varies. The thing I didn't like most about this was how he picked apart the drummer analogy. I felt like it was a low blow and not really necessary.