Zend Framework: The Cost of Flexibility is Complexity

The Zend Framework is a very flexible system, in fact, it can be used to build practically anything. But, the problem with using a flexible system is that flexibility costs. And the cost of flexibility is complexity.

Martin Fowler wrote:

Every time you put extra stuff into your code to make it more flexible, you are usually adding more complexity. If your guess about the flexibility needs of your software is correct, then you are ahead of the game. You’ve gained. But if you get it wrong, you’ve only added complexity that makes it more difficult to change your software.

Don’t assume that just because you’re using an object-oriented framework you are writing reusable and maintainable code. You are just structuring your spaghetti code. As Paul Graham put it: “Object-oriented programming offers a sustainable way to write spaghetti code”. The main problem with flexibility is that most developers give up trying to understand. I don’t blame them, no one likes dealing with complexity. However, flexibility is sometimes required and generally always desirable. But, sometimes we forget that frameworks exist to help us solve or address complex issues, not to fix bad design decisions for us. So if your code is difficult to understand or unit test, then you are probably doing something wrong.

Just because you can, doesn’t mean you should

What’s the best way to render a view script in the Zend Framework? Think, don’t give up yet. Is it enabling or disabling the ViewRenderer action helper? Remember, the framework allows you to shape the Zend_Controller_Action class to your application’s needs.

The manual clearly states that:

By default, the front controller enables the ViewRenderer action helper. This helper takes care of injecting the view object into the controller, as well as automatically rendering views.

However, no one stops you from doing it manually:

Zend_Controller_Action provides a rudimentary and flexible mechanism for view integration. Two methods accomplish this, initView() and render(); the former method lazy-loads the $view public property, and the latter renders a view based on the current requested action, using the directory hierarchy to determine the script path.

Developer A

class IndexController extends Zend_Controller_Action
{
    public function searchAction()
    {
        $query = $this->_getParam('q', null);
        if (null === $query) {
            $this->view->error = 'Invalid query';
            return;
        }

        $this->view->query = $query;
        if (is_numeric($query)) {
            $this->setUserDetailsById($query);
        } else {
            $this->setUserDetailsByName($query);
        }
    }

    public function setUserDetailsByName($name)
    {
        $row = $this->findUserByName($name);
        $this->view->userId = $row['id'];
        $this->view->userName = $row['name'];
    }

    public function setUserDetailsById($id)
    {
        $row = $this->findUserById($id);
        $this->view->userId = $row['id'];
        $this->view->userName = $row['name'];
    }
}

Developer B

class IndexController extends Zend_Controller_Action
{
    protected $_viewParams = array(
        'error'    => null,
        'query'    => null,
        'userId'   => null,
        'userName' => null,
    );

    public function init()
    {
        $this->_helper->removeHelper('viewRenderer');
    }

    public function searchAction()
    {
        $query = $this->_getParam('q', null);
        if (null === $query) {
            $this->_viewParams['error'] = 'Invalid query';
            $this->renderView('search');
            return false;
        }

        return $this->displayUserDetails($query);
    }

    public function displayUserDetails($query)
    {
        if (is_numeric($query)) {
            $row = $this->getUserDetailsById($query);
        } else {
            $row = $this->getUserDetailsByName($query);
        }

        if (!$row) {
            $this->_viewParams['error'] = 'User not found';
            $this->renderView('search');
            return false;
        }

        foreach ($this->_viewParams as $key => $value) {
            if (!array_key_exists($key, $row) {
                continue;
            }
            $this->_viewParams[$key] = $row[$key];
        }

        $this->_viewParams['query'] = $query;
        $this->renderView('search');

        return true;
    }

    public function renderView($template)
    {
        $view = $this->initView();
        foreach ($this->_viewParams as $key => $value) {
            $view->$key = $value;
        }
        $this->render($template);
    }

    public function getUserDetailsByName($name)
    {
        return $this->findUserByName($name);
    }

    public function getUserDetailsById($id)
    {
        return $this->findUserById($id);
    }
}

Same problem, different solutions.

Now, guess who is doing test-driven development, has all his code unit tested, puts commonly used code into libraries, abstracts out common patterns of behaviour, writes his code using the top-down fashion and uses the principle of “least astonishment”.

Developer A or B?

Conclusion

Many requirements lie in the future and are unknowable at the time our application is designed and built. To avoid burdensome maintenance costs developers must therefore rely on a system’s ability to change gracefully its flexibility. The combination of flexibility and smart design can reduce the maintenance burden. It’s up to us, the developers, to change our thinking when using a flexible framework. Like I said before, frameworks exist to help us solve complex issues, not to make decisions for us.

About these ads

30 thoughts on “Zend Framework: The Cost of Flexibility is Complexity

  1. Just because you can, doesn’t mean you should. Sure, Zend Framework is very flexible and lets you build stuff in all sorts of ways. Should you take ZF up on this flexibility offer just because it’s there? No, stick with the documented best practices. Don’t configure ZF to work differently just because you “prefer” it to work that way – unless you’ve got a good reason. That flexibility is there because somebody *did* have a good reason and *needed* that flexibility. Your software will be more maintainable if you just follow the documented best practices. If you use ZF’s flexibility just because you feel like it then you’re making your software less maintainable. If you’re using that flexibility because you need it then make sure the functionality gained is worth the degradation of maintainability. As always, it’s your choice.

  2. The keyword here is really “Just because you can doesn’t mean you should”, as Bradley said. I’d even go as far as say that when possible, there should only be one way of doing one thing. Having a non flexible but well studied solution to a common problem is much better than a flexible but less interesting solution.

    What do you prefer, a simple and elegant solution that gets the work done fast or a complex but flexible solution that gets the work done using more time (and usually more code) but that you might use *or not* one day?

    If you can’t foresee using the flexibility in your developments, go with the simple solution. Always go with the simple solution. Your development will be perfect only when it can’t be made any simpler.

  3. I wouldn’t say Developer A doesn’t know about TDD. With Zend_Test_PHPUnit, it’s actually pretty easy to test the results of your actions — you can assert against various structures and content in the final response.

    I *would* argue that Developer B is going to need to write many _more_ tests than Developer A, however, as they are introducing new functionality and behaviors, instead of relying on the framework.

    But, to the main topic of the post: Terry Chay once said something that resonated with me — one of the few things, actually — and it was something like this: complex > complicated, and that simple does not necessarily mean “not complex”. The point is that you can have a complex implementation that covers many use cases, without the implementation being complicated, and with the API still being relatively simple. Developer B is leveraging that here, as the API for using an alternate implementation is relatively simple.

    Regarding Loic’s comment, “when possible, there should only be one way of doing one thing,” I’m of two minds. Many frameworks have successfully done just that (RoR, CakePHP). However, this also has its costs: if you need to integrate legacy code, or have slightly different requirements, this type of methodology will limit users in the end. A more flexibly API, while more complex, makes integration possible.

  4. I don’t see the ‘documented best practices’ anywhere. Perhaps they’re someplace in the hundreds and hundreds of pages of ‘documentation’ but that’s not terribly useful.

    A ‘recommended’ way of doing things, by way of a sample application that people could get and study would be more useful. And I don’t just mean – “oh go grab this and look at it” type things that are floating around. I mean something that is specifically endorsed by the ZF team as being the canonical way of doing things. Sure, of course, yes, fine, be “flexible” all you want, but understand what it is you’re being “flexible” around in the first place.

    There’s a reason Rails took off the way it did, and it didn’t do it by putting “flexibility” as the primary objective. Yet, you’ve always been able to be flexible in Rails, and will even be moreso when the Merb project is merged back in with Rails (soon?).

    I’ve yet to run across different ZF projects by the same people that were set up similarly enough not to cause me problems when trying to figure out how the heck they worked. I’ve worked with code written by a Zend employee using the Zend Framework that didn’t even follow the default “view” behaviour, and there was no good reason (I could see) – it was just his preference to do things his way.

  5. Hi Matthew, I agree with you.

    The point I’m trying to make is that by using the ViewRenderer helper class, those who are not familiar with the framework have to predict the results.

    The ViewRendered helper:

    > Eliminates the need to instantiate view objects within controllers.

    I prefer a more transparent way of registering view objects with the controller, assuming that not everyone is familiar with the code I write and/or framework I use.

    > Creates a globally available view object for all dispatched controllers and actions.

    This can also be a disadvantage, as it makes it easier to break some fundamental programming rules and write spaghetti code.

    However, I’m well aware that enabling the ViewRenderer helper by default, can help lower the learning curve and entry barrier.

  6. One point not mentioned though is the question of bloat? I don’t mean at the application level however at the framework level if you follow :)

    With that complexity (lets use ZF as an example) surely you have an increase in bloat no? I don’t use ZF simply because I have my own lean (mean?) framework but I’ve kept an eye on it since the early days – and it’s (very) fat, isn’t it?!

    As for unit testing, looks like Developer B is doing a better job of it than what Developer A ever could ;)

    Good post by the way.

  7. @Matthew: as long as it’s object oriented and that you separate the creation of objects in a method in your classes (or make a registry or whatever) then extending the framework’s classes should be the only thing you have to do to suit your needs.

    If you can’t then you probably really aren’t using the right tool for this specific task. You should use the right tool for the right job, not try to integrate everything under framework X just because you can.

    About simple/complex/complicated, you’re right. I’m not sure who said that but someone once said “simple is better than complex but complex is better than complicated”. Sometimes the simplest solution is complex but it’s still better than a complicated one.

  8. @loic – When something is touted as “flexible”, wouldn’t someone think that perhaps it could be flexed to meet their needs?

    This mythical “right tool for the right job” is, for most people, pretty much useless advice. Most people don’t have enough experience with large numbers of tools to know what the differences. Add to that that most people don’t really understand their problems at the start of a project all that well, and “right tool for the right job” becomes meaningless.

    Also, which definition of ‘right’ do you use? In a shop heavily invested in MS, the ‘right’ answer is going to be MS technology, even if *I* can solve problem X with a LAMP stack and webservices integration faster than building something from scratch in ASP.NET. Why? Because the shop’s needs (reuse of developers, long term support, etc.) usually trump the short term talents/interests of one developer on the team.

    Throw out “HEY! THIS TOOL IS FLEXIBLE!” enough times and people will use it, even if it’s not a great fit, because they think they’ll be able to handle whatever problems come down the line.

  9. @mgkimsal We are working on the best practices documentation, but there are already a number of resources: the QuickStart guide online, a number of our conference and webinar sessions are published online, and more. As for what happens internally at Zend — that’s also a facet of our best practices that we’re working on (unifying the messaging about Zend Framework).

    Part of our flexibility lies in the fact that most components are *not* tightly coupled — and so we are having to determine the appropriate ways to introduce coupling or document how to get them inter-operating with one another. And this is where we could definitely do better.

  10. @mgkimsal Zend Framework adapts very well to different requirements. I have used it in the past to build from small Web applications, to Web services, Mashup applications and even a large-scale CMS. So there’s no doubt it’s very flexible, mainly thanks to its use-at-will architecture.

  11. @mgkimsal – Of course you are right, but I was speaking in a very general way and not only about ZF or flexible tools. I was pointing out that if making your code run on a framework requires you to do more work than just extending (and rewrite a few methods at most), then it’s probably not a good idea and there ought to be a better tool for your purpose.

    One thing that comes to mind about this is the tip that was posted on various blogs a few weeks ago about improving performance by removing all the require_once from the ZF files and using autoload instead. If you have to go so far that you have to edit all the framework’s files to reach your goal then it’s probably not the right tool for the job.

  12. Pingback: Andy Sowards

  13. @Les Please define the word ‘bloat’ as you use it here. I’d really like to know why people feel this is the case. Oftentimes it’s because of the package size, for which I’ve never seen a significant impact on application development in the context of use-at-will architectures. Do you mean something else?

    @mgkimsal There are situations in which having flexibility you don’t immediate need is important. As a decision maker on a project- a role I happen to have taken on many times- you are often very interested in the ‘worst case scenarios’. IE, what if this requirement came up that doesn’t jive with our conventions, or what happens if my requirements outgrow my framework? The additional flexibility is very important for risk management in this case. These considerations shouldn’t outweigh day-to-day development, however, and with the right software I believe you can have the best of both worlds.

    @Tarique Sani I’m down with opinionated software, but much less so with opinionated frameworks. For example, who cares whether the authors of Rails (not to pick on them- this is just the best example I know of) think REST is better than SOAP when you’ve got to integrate your app with a web service that is based on SOAP by EOD today? The fact is both are out there in the wild, and I find it hard to see how a framework can express an ‘opinion’ like this without significantly limiting the applications that are built on it. In the ZF project, we’d rather leave the opinions up to the developer and provide suggestions based on our own experience. These typically manifest as sensible defaults.

    @Federico At the most basic level, I simply don’t see complexity and flexibility as a trade off (to put your thesis in completely over-simplified terms ;) ). I would describe this as a ‘false tradeoff’ that is widely assumed but can easily be dispelled with the right counterexample. On the ZF project, we’re trying to provide a counterexample to many such false tradeoffs; whether we’ve done this effectively or not is a different matter. Ultimately, you could have made the same argument using practically any technology. That is to say, it isn’t that hard to obfuscate any design. I can easily show you how to do it in a Rails, Django, etc. application out there. Start by replacing all newlines with spaces in your app. ;) It is inevitable that one must rely on human intelligence and judgment for good, maintainable design, otherwise we’d all be out of jobs while our computers programmed themselves.
    For ZF, the core team endeavors to provide some of that judgment while not enforcing it. Again, that’s just our goal; how we deliver on that is up to the community to decide.

    ,Wil

  14. > It is inevitable that one must rely on human intelligence and judgment for good, maintainable design

    Yes, that’s the point I was trying to make. I see flexibility as an advantage. The combination of flexibility and smart design can reduce the maintenance burden. It’s up to us, the developers, to change our thinking when using a flexible framework. Like I said before, frameworks exist to help us solve complex issues, not to make decisions for us.

  15. @Wil Using an opinionated framework is purely a business decision for me rather than a technical one. At the EOD I have/had a greater iteration of programmers hopping jobs than my framework of choice going away, add to it the problems of every rockstar wanting to perform / code his own way….

    That said I have been following ZF since very very early days and it has now come to a point where I may really use it in a commercial project ;-)

  16. “Like I said before, frameworks exist to help us solve complex issues, not to make decisions for us.”

    I would say just the opposite. Frameworks exist to make default decisions for us and give order to disorder. Libraries exist to help us solve complex issues (image processing, language processing, etc etc)

    That’s like saying, I want to build a house, but I don’t want to use any blueprints because they force too many decisions on me. The flexibility of a hammer and nails is all I need to build a house. Well… that’s the whole point of blueprints, to make sure you perform something to guidelines, safety tolerances, etc etc.

  17. Pingback: phpprotip.com

  18. Pingback: pcsourcenetwork.com

  19. Pingback: Top Posts « WordPress.com

  20. Pingback: Implementing your own Front Controller in Zend Framework « fede.carg ( blog )

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s