It’s quite surprising how simply stating the intent allows for improvements. In the last few days, I have been rewriting the permission system for TikiWiki. One of the goals was to add consistency in the way it works with category permissions in regards to object and global permissions, the other was to improve performance.
Typically, listing elements would contain a condition for each element to verify if the user is allowed to view the object. This seems perfectly reasonable and innocent. However, the implementation of that function turned out to be a few hundreds of lines over time, including function calls all seemingly innocent. However, they were not. The amount of database queries performed to filter the list was unreasonable.
Was it lack of design, careless implementation or the desire to keep things simple? Certainly, a lot of re-use was made. The function to verify permission was so convenient that it could be used anywhere in the code. Everywhere a list had to be filtered, two lines were added in the loop to filter the data. Simple, innocent. Why would you bother placing that in a function? It’s just two lines of code. Plus! Placing it in a function would harm performance because it would require an additional loop.
The cost of the look is irrelevant. Stating the intent allows for something much more powerful than just simplifying those loops all around, it allows for creating a better way to filter the list when that is what needs to be done, like bulk loading the information required to resolve the permissions, diminishing the amount of queries required by an order of magnitude or two and offering a sane point to cache results and avoid queries altogether.
Even a naive implementation with terrible overhead of a loop would have been useful. Certainly, writing code to load large amounts of information in a single batch is harder. It requires understanding the relationships and the entire flow. However, if the right abstraction exists, stating the appropriate intent, optimizing the filtering routine can be done at a single place rather than requiring all the code to be updated.
This is one of the pitfalls of object oriented programming, and even procedural programming for that matter. It’s very easy to create elegant abstractions that hides the implementation details. It’s very easy to use, but when used in the wrong context, it creates slow and bloated applications. The abstractions need to reflect the tasks to be performed, not be regrouped around what holds the tasks together.
Focus is too often placed on the implementation details and local optimizations, while the big differences are made at a much higher level by correctly sequencing the operations to be made. Once the right abstractions are in place, when the interfaces are defined, the actual implementation is irrelevant. If it turns out to be slow due to the database design, it can be changed without affecting the rest of the application. The intent of the code remains unchanged.
It also makes the code more readable. It becomes like reading an executive summary. It tells you the outline and the important pieces to remember, but it does not bury you with details. It provides a high level view that, in most cases, is what people need to understand. Sure, understanding abstractions is a different kind of gymnastic for the brain when you need to debug a piece of code, but most of the time, you can just read by and ignore the details.