It is not the first time that someone asks the question: which framework should I use. The short answer is: it depends. The long answer is more complex and IMHO there will never be the one (and only) winning framework. This is not an answer to this article (The Curious Coder’s Java Web Frameworks Comparison: Spring MVC, Grails, Vaadin, GWT, Wicket, Play, Struts and JSF), but an different view on the same subject.
After I read the article, I was sure that I can not agree with the conclusion (and with some of the numbers they made). I thought about this some days (and more days). After a short time i thought “my numbers” will not help in any way. So I will not give you any numbers. Instead I give you some answers to questions which I think you should ask. you should not take my explanations as a fact. It is and it will ever be an opinion, my opinion. But maybe it is helpful in some way.
Back to the Problem
Choosing a framework, a library or technology is a common challenge, an important part of most development activity. So one might think, that there must be a simple answer, a simple rule to choose the right one. But as you might guess, there is no such rule. And I believe there will be no such rule, because there is no easy way to make a rule which fits for all. But I can show you some of my rules I follow if I have to choose, if I have to decide something. If you expect something new and breathtaking: there will be nothing like that. But maybe I can tell you something that you already know…
But I have to start “far away” from the initial question to get to my point: the lifecycle of software development.
I did develop different kind of applications for some years now. As much as each application differ from the others, the life cycle of these applications were nearly the same. So I will try to show how I think the life cycle of most software development projects looks like.
You start with some new idea and write a lot of new code. Then you have reached some first step and push your product out to the customer. On the “happy” path you will start with some refactoring, because after you “done” you know much better how you should have done this. So after you “done” the first time, you push your “old” code around to make it fit better for what you think it is “right”. You create new features which are easy to implement, because your code is in some kind of fluid state, where changes are easy. This can happen for a long time until the project reached its (happy) end.
Not so happy?
But there is the “not so happy” path. And this is how it goes: If you succeed in building a first release, then with this first step the game will change (How can you tell that you succeed? This is different story.). Now there will be a lot of pressure to “drive the car as fast as you can”, to put new features into your product because someone thinks that only this way you can increase the success. There are deadlines out of nothing where every feature is a must-have. This is the time where most of the agreements from yesterday are forgotten. Maybe you will hear such things like “We do it after the next release.” or “Yes, we said we will do this right after… but not this time.”. With that you will write again a lot of new code. … and so on and on… Deep inside you know, that you should avoid this to happen at all costs, but often you are not in the position to do so.
Maybe you think and believe (maybe hope), that you can do all this refactoring “later” (or somebody is pushing you enough with “we have no time/money/resources/what ever”). This way you will (not “maybe you will”, for sure you will) end up in a big ball of mud. This “later” will never happen or will happen in such a “homeopathic” way (“…this needs to long, …we can not invest so much time into it…”), that you will stay with this mud for a very long time. If everybody agrees someday in “we have a big ball of mud” you can throw away some (or all, if you have much luck) parts of this product and start a new life cycle (maybe you put your story at infoq with the title “how we scaled <our-product> at <our-company>”). You are smarter now …but you were not stupid the last time you started, weren’t you?.
How can you tell if you have already a big ball of mud? Maybe this will help you to answer yourself this question: Rotten versus Good Design
Maybe this is some kind of human behavior, that even for “black beauty projects” (your project is a “dead horse”, best described as “black box” where all knowledge has left long time ago and nobody can touch it without breaking it) we are clinging on life.
Love it, change it or leave it
With every new feature you should ask yourself, if you should invest some time in refactoring first. And the answer should always be “yes”. Put as much as needed into refactoring to leave your code in an fresh and liquid state. If you do so, I think it will take about 30-70% of your time. The rest of the time you will think about solving problems and building new stuff.
The less you refactor, the more time it takes to implement new stuff. If you do not refactor at all, the big ball of mud is coming, and you will struggle with defects and bad code while trying to implement new stuff. It will take days for thing where it should take minutes, it will take month instead of days, it will take years instead of weeks.
So if refactoring is this important, what’s the deal with it? But before we can talk about Refactoring we must talk about code.
Its not configuration – its code.
A common misunderstanding: most of the time you think you “configure” something in your preferred framework you are writing code (in some of your framework DSL). If you put a bean definition in your spring xml then this is code. If you put some transition into your webflow xml then this is code. Even templates are code (I talk to you JSF and Grails).
It is more often code then configuration (if you put a server name and port in some text file then this could be configuration, if you alter the number of threads used, this is for sure code). Simply put: If you change the behaviour of your application then it is code (most of the time). If your IDE connect the dots between your code and your “configuration“, you should ask yourself how independent these parts really are.
If you agree with me, that you spring xml is code, than you hopefully agree with me, that its not clean code and that its much easier to copy “code” (or do other bad things) then to write something from scratch.
There are two kinds of refactoring. The one where you don’t change the behaviour of your code (extract method, introduce interfaces and so on) and the other where you change the behaviour, the architecture, the whole meaning.
With a statically typed languages you may have a very good tool support for the first kind of refactoring (and if not the compiler knows if you missed something). Your IDE should know, how to change stuff (and you can do a lot of things with an IDE which you can not do with your favourite text editor). If you prefer dynamic typed languages then (a often heard mantra is that) your high test coverage gives you the confidence that you did not miss a part (do you have a high test coverage?).
Testing can be very effective way to show the presence of bugs,but is hopelessly inadequate for showing their absence. – The Humble Programmer – Edsger W. Dijkstra
The first kind of refactoring lays the ground for your bigger code change. Most of the time you will try to move your code as far as you can to the “new picture” you have in mind and after you reached the limit you start with the second kind of refactoring.
But there are refactoring boundaries, especially if you are using a statically typed language. If you use some kind of reflection, then you can not say for sure which method is called. If you use Spring (with xml) you need some IDE support to rename stuff (and hope the nothing is missed.. how will you notice?). There is no compiler which can tell you that you did not change the behavior of your code with this renaming. So if you change something on both sides of such a boundary something is likely to break.
But where a simple renaming can cause much pain it is sometimes impossible to move code from one side of the boundary to the other side. You have to use the given abstractions even if they don’t match. This will lead to workarounds and improper solutions which will force other workarounds and improper solutions and so on.
Everytime you missed something you will get a little more sensitive to the risk of such a refactoring. And with the every increasing risk you are likely to stop your refactoring ambitions and leave the code untouched. And every time you fail this way the boundary gets bigger, your own code is the new boundary now. Your own code does not match your needs anymore, you will start soon to build your ways around your own code. The boundary gets bigger and bigger. Your code soon starts to smell.
Framework – code you cannot change.
Every framework comes with boundaries. Sometimes its only the API (if you take jodatime as an example) but more often its a impressive collection of different boundaries (you have to implement interfaces, extend from special classes, uses special conventions). So beside the question whether or not a framework can fulfil your needs there is the “hidden cost” question. Which boundaries does this framework impose on my code?
I hope i could make clear that it is better to have no boundaries at all. So every boundary is one too much. But if someone is doing “your job” you have to accept some limits. So what are typical boundaries?
“Dangerous road ahead” – your framework city limits
As i mentioned before most often we see some API, a boundary in the same language you are using. You will call some methods and work with the results. It does not limit your code in any way. You can hide the framework beneath a layer of your own code. Your other code does not know this framework at all (again jodatime as an example). Its very easy to move your code around. This is more a library then a framework. This kind of a library is like a good friend, a helping hand.
But often enough we are using one (or more) framework for complex tasks. We use these frameworks because they promise to save a lot of effort by reusing their “design”. This way we have to deal with more than one API. We have to implement framework interfaces, we must put things in the “right” places. These kind of frameworks are leaking into your code. Its not so easy anymore to move your code around.
Every design is an abstraction over one known problem with the aim to solve many similar problems. Sometimes your problem differ too much, sometimes the design is not done well (try to implement the java List interface to get an idea..), often it is both:) And the more frameworks you are using, the more boundaries your code has to live with. Its getting harder to move code around. And if its getting harder, you start to leave it as it is. And if you leave it as it is, you on your way to the big ball of mud.
Every line of written code comes with hidden costs. So what can we do?
Keep things simple:
- Use as little as possible frameworks and libraries. Reduce your technology stack (languages, topology, interactions, hardware).
- Avoid over engineering. Make it specific before you make it abstract.
- Separate data from behavior.
- Anticipate human error. If something can go wrong, it will.
- Use types as much as possible. Make them immutable. Avoid unit tests when you can express it as a type.
- Do not use primitive types in interfaces. Avoid primitive types if you can.
- Fail fast. Validate your parameter before you do anything else with them and let it crash if they are wrong.
- Avoid magic (reflection, annotation, byte code manipulation,code in templates, …).
Did I miss something? Yes, for sure. Do you think, that I am wrong? Then tell me why:)