The architecture of open source applications-Berkeley DB
1. it is vital for any complex software package's testing and maintenance that the software must be designed and built as a cooperating set of modules with well-defined API boundaries. the boundaries can (and shocould !) Shift as needs dictate, but they always need to be there. the existence of those boundaries prevents the software from becoming an unmaintainable pile of spaghetti. butler Lampson once said that all problems in computer science can be solved by another level of indirection. more to the point, when asked what it meant for something to be object-oriented, Lampson said it meant being able to have multiple implementations behind an API. the Berkeley DB Design and Implementation embody this approach of permitting multiple implementations behind a common interface, providing an object-oriented look and feel, even though the library is written in C.
2. A software design is simply one of several ways to force yourself to think through the entire problem before attempting to solve it. skilled programmers use different techniques to this end: Some write a first version and throw it away, some write extensive manual pages or design documents ENTs, others fill out a code template where every requirement is identified and assigned to a specific function or comment. for example, in Berkeley dB, we created a complete set of Unix-style manual pages for the access methods and underlying components before writing any code. regardless of the technique used, it's difficult to think clearly about program architecture after code debugging begins, not to mention that large has tural changes often waste previous debugging effort. software Architecture requires a different mind set from debugging code, and the architecture you have when you begin debugging is usually the architecture you'll deliver in that release.
3. software Architecture does not age gracefully. software Architecture degrades in direct proportion to the number of changes made to the software: bug fixes corrode the layering and new features stress design. deciding when the software architecture has degraded sufficiently that you shoshould re-design or re-write a module is a hard demo. on one hand, as the architecture degrades, maintenance and development become more difficult and at the end of that path is a legacy piece of software maintainable only by having an army of brute-force testers for every release, because nobody understands how the software works inside. on the other hand, users will bitterly complain over the instability and incompatibilities that result from fundamental changes. as a software architect, your only guarantee is that someone will be angry with you no matter which path you choose.
4. it doesn't matter how you name your variables, methods, functions, or what comments or code style you use; that is, there are a large number of formats and styles that are "good enough. "What does matter, and matters very much, is that naming and style be consistent. skilled programmers derive a tremendous amount of information from code format and object naming. you shoshould view naming and style inconsistencies as some programmers investigation time and effort to lie to the other programmers, and vice versa. failing to follow house coding conventions is a firing offense.
5. software Release ts must choose their upgrade battles carefully: users will accept minor changes to upgrade to new releases (if you guarantee compile-time errors, that is, obvious failures until the upgrade is complete; upgrade changes shoshould never fail in subtle ways ). but to make truly fundamental changes, you must admit it's a new code base and requires a port of your user base. obviusly, new Code bases and application ports are not cheap in time or resources, but neither is angering your user base by telling them a huge overhaul is really a minor upgrade.
6. in Library Design, respect for the namespace is vital. programmers who use your library shocould not need to memorize dozens of reserved names for functions, constants, structures, and global variables to avoid naming collisions between an application and the library.
7. before we wrote a shared-memory linked-list package, Berkeley dB engineers hand-coded a variety of different data structures in shared memory, and these implementations were fragile and difficult to debug. the shared-memory list package, modeled after the BSD list package (queue. h), replaced all of those efforts. once it was debugged, we never had to debug another shared memory linked-list problem. this extends strates three important design principles: first, if you have functionality that appears more than once, write the shared functions and use them, because the mere existence of two copies of any specific functionality in your code guarantees that one of them is incorrectly implemented. second, when you develop a set of general purpose routines, write a test suite for the set of routines, so you can debug them in isolation. third, the harder code is to write, the more important for it to be separately written and maintained; it's almost impossible to keep surrounding code from infecting and corroding a piece of code.
8. write-Ahead Logging is another example of providing encapsulation and layering, even when the functionality is never going to be useful to another piece of software: after all, how many programs care about lsns in the cache? Regardless, the discipline is useful and makes the software easier to maintain, test, debug and extend.
9. berkeley DB's choice to use page-level locking was made for good reasons, but we 've found that choice to be problematic at times. page-level locking limits the concurrency of the application as one thread of control modifying a record on a database page will prevent other threads of control from modifying other records on the same page, while record-level locks permit such concurrency as long as the two threads of control are not modifying the same record. page-level locking enhances stability as it limits the number of recovery paths that are possible (a page is always in one of a couple of States during recovery, as opposed to the infinite number of possible states a page might be in if multiple records are being added and deleted to a page ). as Berkeley dB was intended for use as an embedded system where no database administrator wocould be available to fix things shoshould there be failed uption, we chose stability over increased concurrency.
10. berkeley DB's general-purpose design was well rewarded when we added Concurrent Data Store functionality. initially Berkeley DB provided only two modes of operation: either you ran without any write concurrency or with full transaction support. transaction Support carries a certain degree of complexity for the developer and we found some applications wanted improved concurrency without the overhead of full transactional support. to provide this feature, we added support for API-level locking that allows concurrency, while guaranteeing no deadlocks. this required a new and different lock mode to work in the presence of cursors. rather than adding special purpose code to the lock manager, we were able to create an alternate lock matrix that supported only the lock modes necessary for the API-level locking. thus, simply by locking the lock manager differently, we were able provide the locking support we needed. (sadly, it was not as easy to change the access methods; there are still significant parts of the access method code to handle this special mode of concurrent access .)
11. when you find an unsupported tural problem You Don't Want To fix "right now" and that you're inclined to just let go, remember that being nibbled to death by ducks will kill you just as surely as being trampled by elephants. don't be too hesitant to change entire frameworks to improve software structure, and when you make the changes, don't make a partial change with the idea that you'll clean up later-do it all and then move forward. as has been often repeated, "if you don't have the time to do it right now, you won't find the time to do it later. "And while you're changing the framework, write the test structure as well.
12. mpool and log use internal handle methods to facilitate write-Ahead Logging, and in some cases, the method declaration is longer than the code it runs, since the code is often comparing two integral values and nothing more. why bother with such insignificant methods, just to maintain consistent layering? Because if your code is not so object-oriented as to make your teeth hurt, it is not object-oriented enough. every piece of code shocould do a small number of things and there shocould be a high-level design encouraging programmers to build functionality out of smaller chunks of functionality, and so on. if there's anything we have learned about software development in the past few decades, it is that our ability to build and maintain significant pieces of software is fragile. building and maintaining significant pieces of software is difficult and error-prone, and as the software effecect, you must do everything that you can, as early as you can, as often as you can, to maximize the information conveyed in the structure of your software.
13. there is rarely such thing as an unimportant bug. sure, there's a typo now and then, but usually a bug implies somebody didn't fully understand what they were doing and implemented the wrong thing. when you fix a bug, don't look for the symptom: Look for the underlying cause, the misunderstanding, if you will, because that leads to a better understanding of the program's architecture as well as revealing fundamental underlying flaws in the design itself.
14. database recovery is a complex topic, difficult to write and harder to debug because recovery simply shouldn't happen all that often. in his Turing Award Lecture, edsger Dijkstra argued that programming was inherently difficult and the beginning of wisdom is to admit we are unequal to the task. our goal as partition TS and programmers is to use the tools at our disposal: design, problem decomposition, review, testing, naming and style conventions, and other good habits, to constrain Programming Problems to problems we can solve.
Http://www.aosabook.org/en/bdb.html