While most of the software development world has been rushing headlong into mountains of awesome frameworks over the last decade-and-then-some, I have been quietly reinventing the wheel. At least, that is how a lot of developers often see me. I do not like frameworks. I avoid using frameworks. When I am forced to use frameworks, I do not enjoy it. I find frameworks only cause me pain and suffering.
Reusable code is not a bad idea. I am a big fan of well written libraries that save me significant time and energy. That is not what frameworks are though. I have seen well written frameworks, but the truth is, it tends to be a toss up. Your odds of getting a well written framework are not better than your odds of getting a poorly written one. While I have rarely had to peak into the guts of a framework (a distinct benefit of avoiding them), those that I have had to dive into have not been good. On one occasion, a family member offered me a decent bounty to find and fix a bug in a popular framework. I spent a few hours trying to get familiar with the code of the framework, but in the end, I realized that I would have to charge several times the bounty offered to make it worth my time. Over the course of a few hours, I was unable to trace the path through the code that the program was taking. It was not that the framework was particularly complex. I can handle complex code. It was just poorly written. It might have looked like a nice framework for people who would not have understood what was under the hood regardless of quality, but for an experienced software developer, it was absolute garbage. On another occasion, I was doing some QA work for a corporation, and I needed to get a little bit more information out of their automated testing system. Of course, they were using a reasonably popular framework for the automated testing. It took me days of combing through the framework to figure out where everything was happening, so I could add the critical code to provide the required data. Again, it was not a matter of complexity. The problem was that whoever wrote the code was doing things in the most roundabout way possible. There was a massive amount of coupling. Everything was touching everything else, and consequently, to understand one thing, I had to understand everything. Other frameworks I have used are often buggy, and the generally poor design and code quality make it not worth the effort to try to fix the bugs myself. I have also had a few occasions to look inside general purpose libraries, and I typically find them to be of much higher quality. This is not always the case, but when I discover a bug in a library, I do not feel the same anxiety when considering whether to try to fix it myself or not. So the problem is not that reusable code is just a lousy idea. It is a great idea. The problem is that frameworks specifically tend to be low quality.
There are a lot of reasons I avoid frameworks. The worst is the fact that most frameworks require a lot of effort to do even the simplest things. The theory is that frameworks make everything easier. The reality is that they tend to make hard things significantly easier and easy things much harder. For example, the straw that broke the camel's back (and pushed me over the edge, into writing this article) is the ESP-IDF framework for programming the ESP32 micro-controller. I would not have used the framework voluntarily (I actually prefer manipulating the hardware directly), but it turns out enough of the ESP32 is proprietary, and the FreeRTOS real-time operating system only has the framework implementation available, so I have no other option. The ESP32 is an excellent micro-controller, with built in wifi and a lot of other really nice features for its price. The framework, however, is a disaster. For a previous project, I used I2C on an Arduino, which I programmed entirely in C. Because I did not want to write my own I2C driver, I took Arduino's driver and ported it to C. This was mildly challenging, but despite excessive complexity, Arduino's code was written well enough that it was not too much of a chore. Now I am writing the same program for the ESP32, and the ESP-IDF framework's I2C driver is a nightmare. I do not even have to port it to C. It is already in C. Somehow though, it is actually more work to use than it was to port Arduino's driver to C and use that. The fact is, I2C is trivial. It is not a complicated protocol. (It is not terribly well designed, but it is not complicated either.) The most difficult part of porting the Arduino driver was eliminating all of the unnecessary object orientation. ESP-IDF is written for C, so it does not even have object orientation, and yet it manages to be several times more complex. The fact is, I2C is such a simple protocol that the most difficult part of using it should be setting up the pins on your chip and configuring the clock speed, if your chip has a built in I2C controller (the ESP32 does), but that is not how it works with ESP-IDF. No, setting up the I2C device is fairly simple (though, the documentation does nothing to help, and the provided example code is stupidly complex). The first step is setting up a struct to tell it which pins to use, how to initialize them, what mode to use, and what clock speed to use. The second and third steps are a pair of function calls that really could have been rolled into a single function call. One extra function call is not a disaster though, and maybe it is somewhat justified, if you need to enable and disable I2C frequently. That is where the simplicity ends though. With the ported Arduino I2C library, once you have the thing the initialized, you can call a single function to read and a single function to write. You give the functions the address, the command, and a few other parameters, and you are good to go. With ESP-IDF, I2C reads and writes are major productions. First, you have to tell it that you are starting an I2C transaction. Next you have to tell it to start accepting data for the transaction. Then you have to do a pair of write function calls to tell it what you want to write (it may be possible to roll them into one, but all of the examples show it as two). Then you tell it you are done giving it the transaction data. Do not think it has actually sent the command though. After that, you tell it to send the command. Once that is done, you have to tell it to delete the transaction. In Arduino, this is all a single function call. In ESP-IDF, it is a disaster. I2C is a simple protocol! ESP-IDF manages to take something incredibly simple and turn it into something extremely complicated. This is perhaps the biggest reason I hate frameworks. This is not a one time thing either. Even Arduino's I2C driver is overly complicated to use. In the process of porting it, I eliminated a significant amount of setup cruft caused by using an object oriented approach where it is entirely unnecessary. In my experience, every framework has places where it takes something almost trivial and makes it more complicated than it has to be. Libraries sometimes do this too, for example, most high level languages built in libraries make pipes prohibitively hard to use (one of the few places Python really biffs it), but frameworks do this all over the place. Threading libraries tend to do this as well.
Compared to this, most of my other issues with frameworks are trivial. The worst one is that when a framework has a bug, it is a lot harder to fix than a bug in code you wrote yourself, even if the framework is well written, because you are not familiar with the framework's code. I have legitimately abandoned frameworks because they had one critical bug that would have taken me more time to fix than just writing the code myself, from scratch. Now days, I generally just skip the part where I use a framework in the first place. Another is that, in my experience, frameworks often take more time to use than writing code from scratch anyway, because learning a framework takes time. Unless I am going to use most of the features offered by the framework, it is not worth my time to learn it. I have written AJAX functions from scratch many times instead of using JQuery, because it is actually faster to do that than it is to setup JQuery on my web site and look up how to use its (admittedly high quality) AJAX stuff. Frameworks also consume unnecessary space, leading to memory bloat, performance costs, and unnecessary bandwidth costs, unless you are using almost 100% of the features provided. These things may seem trivial, but when you consider the scale on the server side instead of at the individual client, it adds up surprisingly fast. Frameworks also tend to come and go, especially in web development. This means that the framework you are learning today probably will not be worth much to you in a couple of years. It might help you advance in your current company, but most promotion in the software industry is not internal, and that other company you really want to work for probably does not use the framework you are learning for your current job. This is further aggravated by the fact that there are so many frameworks out there that there really is no point learning one that you do not have an immediate application for. Lastly, and this is really more important than the last few issues, frameworks are almost never as general purpose as libraries. Nearly all frameworks are designed with a fairly narrow set of use cases in mind, but then they are advertised with a focus on one design element that is not closely related to those use cases. For example, responsiveness is a popular feature of web frameworks that dynamically load data. Most such frameworks are designed for point-of-sale and inventory management, and they tend to be janky or even downright lousy for anything else. They do not tell you that you should not use them to make a site that needs good quality real-time interaction though. They do not tell you that the responsiveness comes at a heavy performance cost. So you decide to use a framework like AngularJS, because "responsive" sounds like exactly what you want for your browser game, but halfway through, after you have spent many hours learning the framework, you discover that while it might work well for business applications (if you are willing to tolerate the making-easy-things-excessively-complicated problem that it also has), it is a horrible choice for games, because what they mean by "responsive" is not the same as what you thought it meant. (No, I did not actually try to use AngularJS for a browser game. I did use it for an inventory management system though, where it worked fairly well but where the performance costs occasionally reared their ugly heads in ways that do not matter to an inventory management system but that would make a game unplayable.)
There are a lot of problems with frameworks. Their popularity makes it clear that a lot of people are willing to put up with those problems, for the perceived benefits, but in my experience, most of those benefits are ethereal. Recent research suggests that system and development architectures make a much bigger difference in success rates than development stacks anyway. This means that any benefits gained from using a particular framework will almost certainly be dwarfed by improving team communication and spending more time on quality design work. My personal experience supports this conclusion.
What it all comes down to is that if you really want to use a framework, pick one with features you are going to use, and know what you are getting yourself into. Personally, I find it easier and faster to code the features I want myself, and I find that the time spent finding and learning a suitable framework ultimately costs me more than just writing the code myself. You might say I am reinventing the wheel, but the fact is, when there are a million different kinds of track, you need a million different kinds of wheel if you want all of your trains to run smoothly.
No comments:
Post a Comment