In How Turn Signals Work, Karim Nice writes "Turn signals may be the most underutilized device on a car." I sometimes feel that when I witness drivers who cannot be bothered with indicating their intentions through use of a turn signal. Besides being simple common sense, the importance of using a turn signal to convey intentions to others is documented by multiple groups and individuals including driver safety informational sites, automobile insurance sites, law firms and legal information sites, a dedicated Facebook page, user interface articles/books, travel information sites, and odds of death and accidents statistics sites. Given that the New York State Police has listed failure to use turn signal properly as #3 (of 7) factors causing accidents, it is easy to wonder if people don't know how to use turn signals or why people don't use turn signals.
Coding conventions are much like turn signals in that they are often a preferred and expected form of communication between developers who might not have an easier way to communicate the same information. Unfortunately, some developers neglect and ignore code conventions the same way that some drivers neglect and ignore use of turn signals. This neglect, in both driving and in development, is often the result of similar motivations and can have negative consequences in both cases. In this post, I look at how use of code conventions for developers is like use of turn signals for drivers in terms of communication, consequences of ignoring them, and reasons used to justify ignoring the "common sense" behind both devices.
Increased Communication: Conveying Intentions
The importance of using turn signals to convey intentions is obvious. There is rarely another way, let alone a better way, to warn other drivers of one's intention to make a turn or change lanes. Other drives can make better decisions when they know each others' intentions. We've all likely seen the increased level of potential danger associated with drivers who are too distracted to even know themselves what they're going to do next. The defensive driver uses cues from the other drivers to be better prepared for different scenarios, but the intentional and correct use of turn signals is undoubtedly one of the best ways other drivers can help the defensive driver to know their intentions.
Just as turn signals provide an easy visual mechanism for drivers to use to communicate intent with one another, likewise coding conventions provide an easy form of communication between developers. Simple things such as proper indentation can make reading and maintaining others' code much easier, quicker, and more likely to be interpreted correctly. Likewise, other naming and style conventions can make it easier to communicate intent of variables, methods, and other pieces of code.
Comments are an obvious way to communicate between developers, but they have their drawbacks. While I'm not opposed to all comments, I do acknowledge they have their problems and that some comments are flat-out poor or unnecessary. In fact, one of my favorite software development quotes is "Comments are the deodorant for stinky code." Comments can get easily removed without affecting the executable code. Comments can become obsolete when developers fail to keep them current with the code changes. Naming conventions can get similarly out of synch, but it is less likely because the developer refactoring code using naming conventions is forced to at least look at what he or she is refactoring and realize that the name is becoming obsolete.
Naming conventions have become even more significant in software development in recent years with the rapidly rising popularity of "convention over configuration" (popularized by Ruby on Rails) and "configuration by exception" (popularized by Java Persistence API). These concepts rely on naming conventions to significantly reduce coding effort, but allow greater flexibility beyond the common cases when needed via explicit configuration. The point here is that naming conventions were recognized as a source of huge productivity gains and applied to a greater degree than many of us had used previously.
Consequences of Failure To Use
There are very serious consequences to failure to use a turn signal properly. Fortunately, in most cases, the consequences of failure to effectively use coding conventions are not as serious as those of not using turn signals. However, this does not mean that failure to use coding conventions is not without cost or consequence. It does not take much experience reading or maintaining other developers' code to quickly realize how much less productive one is in maintaining, reading, and refactoring another's (or even one's own) code when it was not written with a convention in mind. When the original developer's intentions are not well understood, it can be much more costly to change the code. In some cases, it may be next to impossible.
More than once, I've witnessed a developer "start all over" with some piece of functionality because understanding the existing code was proving too difficult and expensive. Some of this difficultly lies in strange logic, design pattern abuse, or use of anti-patterns, but lack of uniformity in code presentation can play a significant part in making software unmaintainable.
Dysfunctional Motivations
When I do encounter the driver who doesn't use his or her turn signal, I often wonder why. I have had my suspicions and research has largely confirmed them. In fact, a Response Insurance survey (1999 to 2005) found that over half of all U.S. drivers stated they do not use turn signals when changing lanes. The reasons survey respondents gave for not using their turn signals were surprising to some, but I think many of them are the same reasons some developers do not adhere to conventions. I look at some of these reasons next.
Missing the Point
The Response Insurance survey cited above found that young drivers were more likely to not use their turn signals than older drivers (though neither age group had a lot to brag about). This might be explainable by the fact that older drivers having more experience and close-calls or collisions due at least partly to failure to use a turn signal. Similarly, if new developers are the way I was when I first got out of college, they are not as likely to recognize the real benefits of adhering to coding conventions because in many college courses the only real advantage of following conventions is that associated with instructor expectations and grading schemes. The experienced developer has likely been burned more than once when another developer (or even the same developer) failed to adhere to conventions.
According to the Response Insurance survey, eleven percent of surveyed drivers that did not use turn signals chose not to because they didn't think it was important. The good news about this group is that they can be taught. They'll likely learn through hard experience even if it is when they have to maintain or refactor or read the code someone else wrote without regard to conventions. I first used conventions as a professional largely because my employer and customers mandated it. This isn't a bad way to get into the habit of doing it until the realization of its value comes.
Laziness
I've long suspected that the primary reason drivers don't use their turn signals is sheer laziness. It sounds terrible that people are too lazy to even make the slight movement necessary to turn on a signal, but I cannot say I was surprised to see that the Response Insurance survey found that nearly 1/4 of those who don't use their turn signal cited laziness as the primary reason. I don't think laziness would be the primary reason for as large of a percentage of developers who don't adhere to coding conventions, but it probably does account for a portion of them. Developers might be too lazy to read and learn the conventions or too lazy to make the effort to change old habits.
Insufficient Time
This one is scary in both driving and software development. The Response Insurance survey found that over 40% of drivers who don't use their turn signals cited insufficient time as the reason. It's frightening that drivers are making lane changes and turns with so little forethought that they cannot even signal a couple seconds in advance. I am sure that more than one developer has not adhered to a coding convention because it slows him or her down and he or she does not have time to bother with that. Again, this is frightening. If someone is so rushed that he or she cannot even adhere to a coding convention, the delivered product is likely to lack the forethought in design and the level of testing needed for a quality product.
Not a Habit
After many years of driving and using my turn signal the vast majority of the time, I have gotten to a point where I almost use it subconsciously. It is wired into me to use it when changing lanes or making a turn. Similarly, I find that repetitive use of coding conventions has a similar effect and that it becomes easier and more natural to use a coding convention as I consistently apply it.
It is sometimes tempting to not use a signal when there is seemingly no one around. One problem with this, however, is that if one drives a lot in such situations, one can quickly lose the habit of using the signal and actually build up a habit of turning and changing lanes without a signal. Similarly, there are times when I'm writing a piece of code that I know no one will be using or maintaining and I often think about not using a coding convention in such cases. However, I typically do follow a coding convention in such cases to make sure I stay in habit and because it is often easier to follow my convention habit than to try to temporarily stray from it.
No One Will See It Anyway
I mentioned above that using a turn signal or coding convention even when no one is around can be beneficial in terms of building or maintaining a good habit. There is another reason one might choose to signal or use code conventions when no one else is around or is likely to see the code: it might be a mistaken assumption that no one else is around or that no one else will read/use the code.
Suppose that another vehicle pulled onto a highway without being noticed and made the bad move of staying in the driver's blind spot. The first driver might assume that no one else is on the road and feel like he or she can change lanes without signaling. However, if the driver chooses to signal anyway, the second driver in the blind spot is given at least warning and can choose to do something to help the situation. Both drivers benefit. Similarly, I have seen too much throwaway code become baseline code to believe that any code will never be seen or used again. It seems safer for everyone involved to adhere to coding conventions even in "throwaway" code.
Intentionally Bad Behavior
What surprised many people the most about the Response Insurance survey was that seven percent of those who don't use turn signals stated that the reason for this was to "add excitement" to driving. Even taking into account that some people like to respond to surveys in a way that is more exciting than realistic for them, it is still disconcerting that anyone would behave like this with others' lives on the line. I don't think one runs into many developers who don't use code conventions out due to a level of excitement, but I do think we see developers who might choose to not adhere to coding conventions out of the ill-conceived idea that they have better job security if they make their code indecipherable (and not adhering to code conventions is just one of many ways to do this).
Selfishness
There seems to be an element of selfishness involved when a driver understands that there are communication benefits associated with using the turn signal, but chooses not to use them despite this knowledge. Similarly, the developer who has learned or understands the benefits of coding conventions is also demonstrating selfishness (laziness, job security, etc.) when he or she chooses to not use them despite this knowledge. In driving, this is misdirected because a bad collision may injure the driver choosing not to signal as much or more than the other driver. With code, not adhering to conventions may not result in immediate problems, but in the long run managers, customers, and fellow developers will recognize (not in a good way) that a certain developer refuses to follow standards and that will typically harm that developer's career and respect.
The longer I work in the software development industry, the more I see examples of where an otherwise good software developer is less useful or even not worth having because he or she allows selfish motivations to cloud his or her judgment and to do things that are not best for the customer, the employer, or even for the team. Stubborn resistance to *useful* (emphasis is intentional) coding conventions falls into this category.
Conclusion
With almost half of U.S. drivers not using their turn signal despite the fact that most probably understand that using a turn signal has advantages, it can be frightening to consider the "other driver." Likewise, the "other developer" is often a scary concept, especially when he or she chooses not to use effective software development principles such as adhering to accepted coding conventions. Using the turn signal and using code conventions have well understood benefits and experienced drivers and developers (as well as the best less experienced drivers and developers) tend to use these tools to their advantage. Unfortunately, not all drivers are of the same quality and not all developers are of the same quality. For a variety of reasons (some of which were covered here), some drivers and some developers do not take advantage of these simple mechanisms with significant potential advantages.
Dustin's Software Development Cogitations and Speculations My observations and thoughts concerning software development (general development, Java, JavaFX, Groovy, Flex, ...). Select posts from this blog are syndicated on DZone and Java Code Geeks and were formerly syndicated on JavaWorld.
Monday, April 26, 2010
Wednesday, April 21, 2010
Effective Exception Handling is Covered Effectively in Effective Java
I recently wrote that the chapter in Effective Java devoted to exception handling has been one of the most influential software development chapters I have read. The value of this chapter was reinforced by Elliotte Rusty Harold's blog post Bruce Eckel is Wrong. Harold's main assertion is that Eckel was wrong to write that checked exceptions must be caught at time of encounter because they don't necessarily need to be caught if they are declared as part of the method's
What Harold wrote about checked exceptions is true, but it is really not all that much nicer to have to declare
This post has reiterated in my mind the value of the Effective Java chapter on exception handling. That chapter succinctly and authoritatively discusses effective Java exception handling. The Second Edition includes two chapters of particular relevance here: Item 58 ("Use checked exceptions for recoverable conditions and runtime exceptions for programming errors") and Item 59 ("Avoid unnecessary use of checked exceptions"). These chapters are worth the quick read, but even their titles are telling.
A mix of checked and unchecked exceptions is "effective," but checked exceptions should only be used for "recoverable conditions" and should not be used "unnecessarily." It is my experience that many more exceptions are nonrecoverable than recoverable. If I cannot do anything reasonable with an exception other than log it and quit, there's no reason to force every developer using that API to write a catch block or multiple throws clauses on all calling methods.
The use of checked exceptions only for recoverable conditions has become generally accepted in the Java community not because of laziness (at least not in all cases), but because of practical experience wasting time writing unnecessary code for situations in which nothing particularly useful could be done anyway. I'll just cite one example here. The Spring Framework made JDBC far easier to use in part by providing a richer hierarchy of runtime exceptions that was much easier to use than the single, checked SQLException. See my article Add Some Spring to Your Oracle JDBC for additional information on this.
The Java Tutorial trail entry Unchecked Exceptions - The Controversy enters this debate:
It should be noted that Harold also agrees that "Bruce is absolutely right that you should not catch an exception unless you know what to do with it."
One of the interesting facets of the Groovy language is that all exceptions are treated as runtime exceptions. I do sometimes like to use checked exceptions or explicitly catch checked exceptions because I can actually do something about them, but I admit welcoming the ability to choose for myself when to check and when not to check.
Harold stated that he wrote the post with the charged title because "it’s time to lay [Eckel's] misconceptions to rest once and for all." In my opinion, Eckel's omission of the ability to add a
throws
clause.What Harold wrote about checked exceptions is true, but it is really not all that much nicer to have to declare
throws
clauses on a series of methods when nothing really useful is going to be done with the exception anyway. While it's true that Eckel should have written that checked exceptions do not need to be caught if they are declared in the throws
clause (and Eckel acknowledges this in the comments section), that omission does not really take away from the fact that any "handling" of checked exceptions does add verbosity to the code whether that "handling" is a catch
or a throws
clause. If something helpful can be done because of this extra code, it is justified. If nothing helpful can be done, the extra code is clutter. It's tedious and unnecessary ceremony.This post has reiterated in my mind the value of the Effective Java chapter on exception handling. That chapter succinctly and authoritatively discusses effective Java exception handling. The Second Edition includes two chapters of particular relevance here: Item 58 ("Use checked exceptions for recoverable conditions and runtime exceptions for programming errors") and Item 59 ("Avoid unnecessary use of checked exceptions"). These chapters are worth the quick read, but even their titles are telling.
A mix of checked and unchecked exceptions is "effective," but checked exceptions should only be used for "recoverable conditions" and should not be used "unnecessarily." It is my experience that many more exceptions are nonrecoverable than recoverable. If I cannot do anything reasonable with an exception other than log it and quit, there's no reason to force every developer using that API to write a catch block or multiple throws clauses on all calling methods.
The use of checked exceptions only for recoverable conditions has become generally accepted in the Java community not because of laziness (at least not in all cases), but because of practical experience wasting time writing unnecessary code for situations in which nothing particularly useful could be done anyway. I'll just cite one example here. The Spring Framework made JDBC far easier to use in part by providing a richer hierarchy of runtime exceptions that was much easier to use than the single, checked SQLException. See my article Add Some Spring to Your Oracle JDBC for additional information on this.
The Java Tutorial trail entry Unchecked Exceptions - The Controversy enters this debate:
Generally speaking, do not throw a RuntimeException or create a subclass of RuntimeException simply because you don't want to be bothered with specifying the exceptions your methods can throw. Here's the bottom line guideline: If a client can reasonably be expected to recover from an exception, make it a checked exception. If a client cannot do anything to recover from the exception, make it an unchecked exception.
It should be noted that Harold also agrees that "Bruce is absolutely right that you should not catch an exception unless you know what to do with it."
One of the interesting facets of the Groovy language is that all exceptions are treated as runtime exceptions. I do sometimes like to use checked exceptions or explicitly catch checked exceptions because I can actually do something about them, but I admit welcoming the ability to choose for myself when to check and when not to check.
Harold stated that he wrote the post with the charged title because "it’s time to lay [Eckel's] misconceptions to rest once and for all." In my opinion, Eckel's omission of the ability to add a
throws
clause instead of catching a checked exception at first encounter is not as significant as made out in this post. More importantly, though, this is a validation of my earlier assertion that the Effective Java chapter on exception handling should be the definitive source of information on exception handling for Java developers.
Saturday, April 17, 2010
Java: Oracle Security Alert CVE-2010-0886
Like me, you've probably been prompted multiple times in recent days/weeks to install a new version of the JRE on your favorite machine hosting Java. The principal reason for the latest version, Java 6 Update 20, is related to Oracle Security Alert CVE-2010-0886. Specifically, the update addresses "vulnerabilities in desktop Java running in [32-bit] web browsers."
The previously cited Oracle Security Alert CVE-2010-0886 states, "Oracle strongly recommends that customers upgrade to these releases as soon as possible." The download page for Java 6 Update 20 states, "This release contains critical security updates to the Java runtime." I think this is a good time to download the full Java 6 Update 20 SDK.
In WebStart Changes Between 6u17 to 6u20: Signed Applications Almost Impossible, Johan Compagner writes about issues his organization is seeing with their products and these updates. The comments on this post are good and include a link to this forum thread with the title "Security update breals A LOT OF STUFF!"
A recent ITworld article Nifty Java Bug Could Lead to Attack summarizes Tavis Ormandy's recent post Java Deployment Toolkit Performs Insufficient Validation of Parameters. It's time for me to go download Java 6 Update 20 SDK via Java Web Start JNLP.
The previously cited Oracle Security Alert CVE-2010-0886 states, "Oracle strongly recommends that customers upgrade to these releases as soon as possible." The download page for Java 6 Update 20 states, "This release contains critical security updates to the Java runtime." I think this is a good time to download the full Java 6 Update 20 SDK.
In WebStart Changes Between 6u17 to 6u20: Signed Applications Almost Impossible, Johan Compagner writes about issues his organization is seeing with their products and these updates. The comments on this post are good and include a link to this forum thread with the title "Security update breals A LOT OF STUFF!"
A recent ITworld article Nifty Java Bug Could Lead to Attack summarizes Tavis Ormandy's recent post Java Deployment Toolkit Performs Insufficient Validation of Parameters. It's time for me to go download Java 6 Update 20 SDK via Java Web Start JNLP.
On the Negative Impact of Selfish Software Development
There seems to be many things in life that can be good and beneficial, but can be made less good or even negative when selfishness is introduced. Software development is no exception. In this post, I look at how selfishness can turn normally positive behaviors into dysfunctional behaviors.
Creative Solutions ⇒ Not Invented Here Syndrome
It's often true that analysis of existing libraries, frameworks, and code bases will lead one to legitimately conclude that new software needs to be designed and written to adequately meet customer needs. That's why we develop software. However, it is easy for a selfish developer to take this too far and waste time, energy, and resources creating something that may be no better than what's already available, only slightly better than what's available, or even worse than what's available. Selfish motivations that can lead to this wasted effort include desire to try something new, desire to stroke one's own ego, and desire for recognition for his or her own greatness in bettering what's already there. To the selfish developer, these motivations may be greater than using a more thoroughly tested satisfactory existing solution.
Polyglot Programming / Best Tool for the Job ⇒ Resume-Driven Development
Polyglot programming is all the rage these days and for good reason. Its advantages include the ability to use the right tool for the job, the ability to learn and adopt principles from one language in development of and with another, and the ability to challenge one's way of thinking about software development. I have enjoyed these benefits from using different languages such as C, C++, Java, Groovy, Ruby/JRuby, Perl, JavaScript, Flex/ActionScript, and so forth. However, as the http://www.polyglotprogramming.com/ site even implies, there are "drawbacks" associated with polyglot programming when it's applied incorrectly.
Bill Burke points out some legitimate issues associated with unbridled polyglot programming in his post Polyglotism is the worst idea I ever heard. As much as I love learning and using new languages, I don't think an objective developer can argue that there aren't any drawbacks to applying too many languages in a single environment. [Incidentally, if someone says anything they are promoting has no drawbacks, I find they are not usually worth listening to.]
The problem comes down to individualistic desire to learn new things and to play with shiny new objects being set against what is sometimes for the greater good of the organization or project. It may be that using three different languages is the best mix for me individually, but if the rest of my team only understands one language well, am I really helping my clients by forcing three languages on them? Is it really fair to our client to expect them to be able to hire enough developers, especially for large projects, that know Java, C#, Scala, Clojure, Ruby, JavaScript, F, Perl, and so on?
This, of course, depends on the situation. Small teams with many developers familiar with or willing to learn a myriad of technologies might have a better chance of making a highly polyglot-oriented environment successful. Even there, though, I expect that years of piling new languages on top of new languages on top of new languages might lead to such a mess that no one is really able to maintain it.
Many clients and organizations are willing to pay for vendors to provide integrated products (that's what Oracle seems to be going after as a vendor) just to avoid cobbling together disparate open source projects. Why would they want their developers cobbling together languages, frameworks, and libraries?
I love learning new languages and the allure of being paid to learn them and apply them is enticing. There are times when it is in the interest of the client or employer and that works well for everyone. However, there are times when I think a less selfish developer might realize that as fun and interesting and value-added as learning a new language might be for him or her, it's just not right to use that language in that environment. Most organizations must live with their developed software and maintain that software for much longer than it took to develop it. Therefore, the best developers understand that maintenance is as important as development and that the organization's long term needs should be considered.
I think developers individually benefit greatly from learning multiple languages. I think organizes and projects can benefit as well, though the application of multiple languages in large environments must be more carefully considered.
Willingness to Try New Things ⇒ Resume-Driven Development
The last issue is not limited to polyglot programming. A selfish developer might be more inclined to use a new library, framework, or third-party product more because it is associated with higher compensation or recognition than for what it can do for the project. It is a good idea to never become so mired in a single approach that one does not improve his or her skills, but sometimes existing skills fit just fine.
Avoiding Needless/Wasteful Experimentation ⇒ Maslow's Golden Hammer
I have just written that selfishness can lead developers to sacrifice organizational success for their own desires. However, it can work in the opposite direction as well. Things do improve in the software development landscape as we collectively learn from our experience. IDEs are generally more productive than not having IDEs. Many abstractions do make building and maintaining applications easier. Some languages are better suited for some situations than other languages are.
A selfish developer may decide he or she doesn't want to learn anything new anymore and just wants to use the approach he or she has always used. This too can be dysfunctional and not in the best interest of the organization. I don't write about this issue as much here because I don't think it's as big of a problem as fed by blogs as is the opposite perspective of always chasing the latest fad. That's not to say it's not a big problem. It is, but I doubt anyone in this trap is reading my blog. :)
Quality / Craftsmanship ⇒ Overextended Cost / Schedule
I have found that it is important to the best software developers to be associated with a high quality product and to demonstrate expertise at their craft. This is generally a good thing. However, there is a point where one is unwilling or unable to ever finish a project because he or she is so driven by the need to have the perfect solution.
Confidence ⇒ Unwilling to Learn / Compromise
I have blogged before that I believe confidence is a characteristic of a good software developer. However, there are times when reasonably experienced people with similar expertise are going to differ in opinion. The confident but selfish developer may be less willing to admit that he or she did not have the best solution or could have been wrong in an opinion. The less selfish but confident developer can acknowledge when someone else has an idea that's better or at least comparable. The more frequent situation is where two designs or opinions are of roughly equal merit. In such cases, selfishness on both sides can lead to an impasse. Unselfishness, especially when existing on both sides, can lead to further discussion, negotiation, and the best overall solution.
Conclusion
It sometimes takes only a little selfish interest, even if it's subconscious, to turn good to bad. Indeed, as I look over my previous post Motivations and Explanations for Poor Software Decisions, I cannot help but think that selfishness is related to nearly all of those. It probably would help all of us to ask ourselves before making some decisions, "Am I doing this because it's best for my customer or employer or because it's best for me?" The (currently) single post available at The Unselfish Programmer blog sums it up well (I highly recommend the entire post): "Successful software is made by being unselfish."
Creative Solutions ⇒ Not Invented Here Syndrome
It's often true that analysis of existing libraries, frameworks, and code bases will lead one to legitimately conclude that new software needs to be designed and written to adequately meet customer needs. That's why we develop software. However, it is easy for a selfish developer to take this too far and waste time, energy, and resources creating something that may be no better than what's already available, only slightly better than what's available, or even worse than what's available. Selfish motivations that can lead to this wasted effort include desire to try something new, desire to stroke one's own ego, and desire for recognition for his or her own greatness in bettering what's already there. To the selfish developer, these motivations may be greater than using a more thoroughly tested satisfactory existing solution.
Polyglot Programming / Best Tool for the Job ⇒ Resume-Driven Development
Polyglot programming is all the rage these days and for good reason. Its advantages include the ability to use the right tool for the job, the ability to learn and adopt principles from one language in development of and with another, and the ability to challenge one's way of thinking about software development. I have enjoyed these benefits from using different languages such as C, C++, Java, Groovy, Ruby/JRuby, Perl, JavaScript, Flex/ActionScript, and so forth. However, as the http://www.polyglotprogramming.com/ site even implies, there are "drawbacks" associated with polyglot programming when it's applied incorrectly.
Bill Burke points out some legitimate issues associated with unbridled polyglot programming in his post Polyglotism is the worst idea I ever heard. As much as I love learning and using new languages, I don't think an objective developer can argue that there aren't any drawbacks to applying too many languages in a single environment. [Incidentally, if someone says anything they are promoting has no drawbacks, I find they are not usually worth listening to.]
The problem comes down to individualistic desire to learn new things and to play with shiny new objects being set against what is sometimes for the greater good of the organization or project. It may be that using three different languages is the best mix for me individually, but if the rest of my team only understands one language well, am I really helping my clients by forcing three languages on them? Is it really fair to our client to expect them to be able to hire enough developers, especially for large projects, that know Java, C#, Scala, Clojure, Ruby, JavaScript, F, Perl, and so on?
This, of course, depends on the situation. Small teams with many developers familiar with or willing to learn a myriad of technologies might have a better chance of making a highly polyglot-oriented environment successful. Even there, though, I expect that years of piling new languages on top of new languages on top of new languages might lead to such a mess that no one is really able to maintain it.
Many clients and organizations are willing to pay for vendors to provide integrated products (that's what Oracle seems to be going after as a vendor) just to avoid cobbling together disparate open source projects. Why would they want their developers cobbling together languages, frameworks, and libraries?
I love learning new languages and the allure of being paid to learn them and apply them is enticing. There are times when it is in the interest of the client or employer and that works well for everyone. However, there are times when I think a less selfish developer might realize that as fun and interesting and value-added as learning a new language might be for him or her, it's just not right to use that language in that environment. Most organizations must live with their developed software and maintain that software for much longer than it took to develop it. Therefore, the best developers understand that maintenance is as important as development and that the organization's long term needs should be considered.
I think developers individually benefit greatly from learning multiple languages. I think organizes and projects can benefit as well, though the application of multiple languages in large environments must be more carefully considered.
Willingness to Try New Things ⇒ Resume-Driven Development
The last issue is not limited to polyglot programming. A selfish developer might be more inclined to use a new library, framework, or third-party product more because it is associated with higher compensation or recognition than for what it can do for the project. It is a good idea to never become so mired in a single approach that one does not improve his or her skills, but sometimes existing skills fit just fine.
Avoiding Needless/Wasteful Experimentation ⇒ Maslow's Golden Hammer
I have just written that selfishness can lead developers to sacrifice organizational success for their own desires. However, it can work in the opposite direction as well. Things do improve in the software development landscape as we collectively learn from our experience. IDEs are generally more productive than not having IDEs. Many abstractions do make building and maintaining applications easier. Some languages are better suited for some situations than other languages are.
A selfish developer may decide he or she doesn't want to learn anything new anymore and just wants to use the approach he or she has always used. This too can be dysfunctional and not in the best interest of the organization. I don't write about this issue as much here because I don't think it's as big of a problem as fed by blogs as is the opposite perspective of always chasing the latest fad. That's not to say it's not a big problem. It is, but I doubt anyone in this trap is reading my blog. :)
Quality / Craftsmanship ⇒ Overextended Cost / Schedule
I have found that it is important to the best software developers to be associated with a high quality product and to demonstrate expertise at their craft. This is generally a good thing. However, there is a point where one is unwilling or unable to ever finish a project because he or she is so driven by the need to have the perfect solution.
Confidence ⇒ Unwilling to Learn / Compromise
I have blogged before that I believe confidence is a characteristic of a good software developer. However, there are times when reasonably experienced people with similar expertise are going to differ in opinion. The confident but selfish developer may be less willing to admit that he or she did not have the best solution or could have been wrong in an opinion. The less selfish but confident developer can acknowledge when someone else has an idea that's better or at least comparable. The more frequent situation is where two designs or opinions are of roughly equal merit. In such cases, selfishness on both sides can lead to an impasse. Unselfishness, especially when existing on both sides, can lead to further discussion, negotiation, and the best overall solution.
Conclusion
It sometimes takes only a little selfish interest, even if it's subconscious, to turn good to bad. Indeed, as I look over my previous post Motivations and Explanations for Poor Software Decisions, I cannot help but think that selfishness is related to nearly all of those. It probably would help all of us to ask ourselves before making some decisions, "Am I doing this because it's best for my customer or employer or because it's best for me?" The (currently) single post available at The Unselfish Programmer blog sums it up well (I highly recommend the entire post): "Successful software is made by being unselfish."
Monday, April 12, 2010
The Contains Trap in Java Collections
One of the nasty little traps a Java developer can run into occurs when Collection.contains(Object) is not used with appropriate understanding. I demonstrate this potential trap in this post.
Collection.contains(Object) accepts an Object, which means it essentially accepts an instance of any Java class. This is where the potential trap lies. If one passes in an instance of a class other than the the type of classes that can be stored in a particular collection, it can be that this method will simply return
This is demonstrated in the next code listing.
In the code listing above, the statement printing out "That is a great book!" will never be executed because a Date will never be contained in that Set.
There is no warning condition for this, even with javac's
As the screen snapshot indicates, NetBeans 6.8 provides the nice and fairly clear warning message, "Suspicious call to java.util.Collection.contains: Given object cannot contain instances of Date (expected String)." This is definitely "suspicious" and is almost never what the developer really desired.
It's not necessarily surprising that the
The Javadoc documentation for the Collection, Set, List, and Map interfaces' respective
In the code above, I used a HashSet, which does not throw a
I will now demonstrate the differences in these behaviors with some code samples.
The first class to be used here is the Person class.
Person.java
Another classed used in my examples is the InanimateObject class.
Non-Comparable InanimateObject.java
The main executable class for testing these things is SetContainsExample.
SetContainsExample.java
When the above code is run as-is (without
The
Comparable InanimateObject.java
With
By virtue of
Dealing with the Collection.contains(Object) Issue
In this post, I have demonstrated how the Collection.contains(Object) method (including the Map.containsKey(Object) method) can behave differently depending on how the particular collection implementation decides to throw or not throw the option
1. Careful coding and code reviews might lead to developers or reviewers seeing that an object of an irrelevant class is being passed to the
2. Use of a modern version of NetBeans or of similar tools that flag the "suspicious" behavior can be very helpful.
3. Unit tests combined with code coverage tests can be helpful in identifying seemingly strange flows through the code that can be attributed to issues such as the one described in this post.
4. Collection implementations that do throw
5. Logging the results of calls to
6. Debugging is an obvious way to detect this problem, but is often more time-consuming than some of the approaches addressed earlier.
I generally prefer the first four steps over the last two because the first three steps are more preventative than the last two and the fourth step gives a clear indication of a problem when it occurs.
Collection.contains(Object) accepts an Object, which means it essentially accepts an instance of any Java class. This is where the potential trap lies. If one passes in an instance of a class other than the the type of classes that can be stored in a particular collection, it can be that this method will simply return
false
. This isn't really wrong because a different type than the collection holds is obviously not part of that collection. However, it can be a trap if the developer relies on the returned value from the contains
call to implement other logic.This is demonstrated in the next code listing.
public void demonstrateIllConceivedContainsBasedCode()
{
final Set<String> favoriteChildrensBooks = new HashSet<String>();
favoriteChildrensBooks.add("Mrs. Frisby and the Rats of NIMH");
favoriteChildrensBooks.add("The Penguin that Hated the Cold");
favoriteChildrensBooks.add("The Bears' Vacation");
favoriteChildrensBooks.add("Green Eggs and Ham");
favoriteChildrensBooks.add("A Fish Out of Water");
favoriteChildrensBooks.add("The Lorax");
final Date date = new Date();
if (favoriteChildrensBooks.contains(date))
{
out.println("That is a great book!");
}
}
In the code listing above, the statement printing out "That is a great book!" will never be executed because a Date will never be contained in that Set.
There is no warning condition for this, even with javac's
-Xlint
option set. However, NetBeans 6.8 does provide a warning for this as demonstrated in the next screen snapshot.As the screen snapshot indicates, NetBeans 6.8 provides the nice and fairly clear warning message, "Suspicious call to java.util.Collection.contains: Given object cannot contain instances of Date (expected String)." This is definitely "suspicious" and is almost never what the developer really desired.
It's not necessarily surprising that the
contains
method returns false
rather than some type of error message or exception because it is certainly true that the Set
in this example did not contain the Date
for which the question was asked. One tactic that can be used to have at least a runtime check for the proper class in a call to contains
is to use a collection type that implements the optional throwing of a ClassCastException when appropriate. The Javadoc documentation for the Collection, Set, List, and Map interfaces' respective
contains
methods all state that they throw ClassCastException
"if the type of the specified element is incompatible with this collection (optional)" (Collection), "if the type of the specified element is incompatible with this set (optional)" (Set), "if the type of the specified element is incompatible with this list (optional)" (List), and "if the key is of an inappropriate type for this map (optional) " (Map.containsKey). The most important thing to note is that each of these declares the throwing of ClassCastException
as optional.In the code above, I used a HashSet, which does not throw a
ClassCastException
when an incompatible object type is passed to its contains
method. Indeed, the Javadoc documentation for HashSet.contains(Object) makes no mention of throwing a ClassCastException
. Similarly, LinkedHashSet extends HashSet
and inherits the same contains
as HastSet
. The TreeSet, on the other hand, has Javadoc comments that state that TreeSet.contains(Object) does throw a ClassCastException
"if the specified object cannot be compared with the elements currently in the set." So the TreeSet
does throw an exception when an incomparable object is provided to its contains
method.I will now demonstrate the differences in these behaviors with some code samples.
The first class to be used here is the Person class.
Person.java
/*
* http://marxsoftware.blogspot.com/
*/
package dustin.examples;
import java.io.Serializable;
public final class Person implements Comparable, Serializable
{
private final String lastName;
private final String firstName;
public Person(final String newLastName, final String newFirstName)
{
this.lastName = newLastName;
this.firstName = newFirstName;
}
public String getLastName()
{
return this.lastName;
}
public String getFirstName()
{
return this.firstName;
}
@Override
public boolean equals(Object obj)
{
if (obj == null)
{
return false;
}
if (getClass() != obj.getClass())
{
return false;
}
final Person other = (Person) obj;
if (this.lastName == null ? other.lastName != null : !this.lastName.equals(other.lastName))
{
return false;
}
if (this.firstName == null ? other.firstName != null : !this.firstName.equals(other.firstName))
{
return false;
}
return true;
}
@Override
public int hashCode()
{
int hash = 5;
hash = 59 * hash + (this.lastName != null ? this.lastName.hashCode() : 0);
hash = 59 * hash + (this.firstName != null ? this.firstName.hashCode() : 0);
return hash;
}
public int compareTo(Object anotherPerson) throws ClassCastException
{
if (!(anotherPerson instanceof Person))
{
throw new ClassCastException("A Person object expected.");
}
final Person theOtherPerson = (Person) anotherPerson;
final int lastNameComparisonResult =
this.lastName.compareTo(theOtherPerson.lastName);
return lastNameComparisonResult != 0
? lastNameComparisonResult
: this.firstName.compareTo(theOtherPerson.firstName);
}
@Override
public String toString()
{
return this.firstName + " " + this.lastName;
}
}
Another classed used in my examples is the InanimateObject class.
Non-Comparable InanimateObject.java
/*
* http://marxsoftware.blogspot.com/
*/
package dustin.examples;
public class InanimateObject
{
private final String name;
private final String secondaryName;
private final int yearOfOrigin;
public InanimateObject(
final String newName, final String newSecondaryName, final int newYear)
{
this.name = newName;
this.secondaryName = newSecondaryName;
this.yearOfOrigin = newYear;
}
public String getName()
{
return this.name;
}
public String getSecondaryName()
{
return this.secondaryName;
}
public int getYearOfOrigin()
{
return this.yearOfOrigin;
}
@Override
public boolean equals(Object obj)
{
if (obj == null)
{
return false;
}
if (getClass() != obj.getClass())
{
return false;
}
final InanimateObject other = (InanimateObject) obj;
if (this.name == null ? other.name != null : !this.name.equals(other.name))
{
return false;
}
if (this.yearOfOrigin != other.yearOfOrigin)
{
return false;
}
return true;
}
@Override
public int hashCode()
{
int hash = 3;
hash = 23 * hash + (this.name != null ? this.name.hashCode() : 0);
hash = 23 * hash + this.yearOfOrigin;
return hash;
}
@Override
public String toString()
{
return this.name + " (" + this.secondaryName + "), created in " + this.yearOfOrigin;
}
}
The main executable class for testing these things is SetContainsExample.
SetContainsExample.java
/*
* http://marxsoftware.blogspot.com/
*/
package dustin.examples;
import static java.lang.System.out;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
public class SetContainsExample
{
final Person davidLightman = new Person("Lightman", "David");
final Person willFarmer = new Person("Farmer", "Will");
final Person daveBowman = new Person("Bowman", "Dave");
final Person jerryShaw = new Person("Shaw", "Jerry");
final Person delSpooner = new Person("Spooner", "Del");
final InanimateObject wopr =
new InanimateObject("War Operation Plan Response", "WOPR", 1983);
final InanimateObject ripley =
new InanimateObject("R.I.P.L.E.Y", "R.I.P.L.E.Y", 2008);
final InanimateObject hal =
new InanimateObject("Heuristically programmed ALgorithmic Computer", "HAL9000", 1997);
final InanimateObject ariia =
new InanimateObject("Autonomous Reconnaissance Intelligence Integration Analyst", "ARIIA", 2009);
final InanimateObject viki =
new InanimateObject("Virtual Interactive Kinetic Intelligence", "VIKI", 2035);
public Set<Person> createPeople(final Class setType)
{
Set<Person> people = new HashSet<Person>();
if (validateSetImplementation(setType))
{
if (HashSet.class.equals(setType))
{
people = new HashSet<Person>();
}
else if (LinkedHashSet.class.equals(setType))
{
people = new LinkedHashSet<Person>();
}
else if (TreeSet.class.equals(setType))
{
people = new TreeSet<Person>();
}
else if (EnumSet.class.equals(setType))
{
out.println("ERROR: EnumSet is inappropriate type of Set here.");
}
else
{
out.println("WARNING: " + setType.getName() + " is an unexpected Set implementation.");
}
}
else
{
out.println("WARNING: " + setType.getName() + " is not a Set implementation.");
people = new HashSet<Person>();
}
people.add(davidLightman);
people.add(willFarmer);
people.add(daveBowman);
people.add(jerryShaw);
people.add(delSpooner);
return people;
}
private boolean validateSetImplementation(final Class candidateSetImpl)
{
if (candidateSetImpl.isInterface())
{
throw new IllegalArgumentException(
"Provided setType needs to be an implementation, but an interface ["
+ candidateSetImpl.getName() + "] was provided." );
}
final Class[] implementedInterfaces = candidateSetImpl.getInterfaces();
final List<Class> implementedIFs = Arrays.asList(implementedInterfaces);
return implementedIFs.contains(java.util.Set.class)
|| implementedIFs.contains(java.util.NavigableSet.class)
|| implementedIFs.contains(java.util.SortedSet.class);
}
public void testSetContains(final Set<Person> set, final String title)
{
printHeader(title);
out.println("Chosen Set Implementation: " + set.getClass().getName());
final Person person = davidLightman;
out.println(
set.contains(person)
? person + " is one of my people."
: person + " is NOT one of my people.");
final Person luke = new Person("Skywalker", "Luke");
out.println(
set.contains(luke)
? luke + " is one of my people."
: luke + " is NOT one of my people.");
out.println(
set.contains(wopr)
? wopr + " is one of my people."
: wopr + " is NOT one of my people.");
}
private void printHeader(final String headerText)
{
out.println();
out.println(
"==================================================================");
out.println("== " + headerText);
out.println(
"==================================================================");
}
public static void main(final String[] arguments)
{
final SetContainsExample me = new SetContainsExample();
final Set<Person> peopleHash = me.createPeople(HashSet.class);
me.testSetContains(peopleHash, "HashSet");
final Set<Person> peopleLinkedHash = me.createPeople(LinkedHashSet.class);
me.testSetContains(peopleLinkedHash, "LinkedHashSet");
final Set<Person> peopleTree = me.createPeople(TreeSet.class);
me.testSetContains(peopleTree, "TreeSet");
}
}
When the above code is run as-is (without
InanimateObject
being Comparable
), the output appears as shown in the next screen snapshot.The
ClassCastException
tells us, "dustin.examples.InanimateObject cannot be cast to java.lang.Comparable." This makes sense because that class does not implement Comparable, which is necessary for use with the TreeMap.contains(Object)
method. When made Comparable
, the class looks something like that shown next.Comparable InanimateObject.java
/*
* http://marxsoftware.blogspot.com/
*/
package dustin.examples;
public class InanimateObject implements Comparable
{
private final String name;
private final String secondaryName;
private final int yearOfOrigin;
public InanimateObject(
final String newName, final String newSecondaryName, final int newYear)
{
this.name = newName;
this.secondaryName = newSecondaryName;
this.yearOfOrigin = newYear;
}
public String getName()
{
return this.name;
}
public String getSecondaryName()
{
return this.secondaryName;
}
public int getYearOfOrigin()
{
return this.yearOfOrigin;
}
@Override
public boolean equals(Object obj)
{
if (obj == null)
{
return false;
}
if (getClass() != obj.getClass())
{
return false;
}
final InanimateObject other = (InanimateObject) obj;
if (this.name == null ? other.name != null : !this.name.equals(other.name))
{
return false;
}
if (this.yearOfOrigin != other.yearOfOrigin)
{
return false;
}
return true;
}
@Override
public int hashCode()
{
int hash = 3;
hash = 23 * hash + (this.name != null ? this.name.hashCode() : 0);
hash = 23 * hash + this.yearOfOrigin;
return hash;
}
public int compareTo(Object anotherObject) throws ClassCastException
{
if (!(anotherObject instanceof InanimateObject))
{
throw new ClassCastException("An InanimateObject object expected.");
}
final InanimateObject theOtherObject = (InanimateObject) anotherObject;
final int yearOfOriginComparisonResult =
this.yearOfOrigin - theOtherObject.yearOfOrigin;
return yearOfOriginComparisonResult != 0
? yearOfOriginComparisonResult
: this.name.compareTo(theOtherObject.name);
}
@Override
public String toString()
{
return this.name + " (" + this.secondaryName + "), created in " + this.yearOfOrigin;
}
}
With
InanimateObject
now implementing Comparable
, the error message when trying to call Set.contains(Object)
on an object whose type does not compare well with that type held in that Set
can now be seen. In fact, the message now seen this the one provided in the compareTo
method of the InanimateObject
. This is demonstrated in the next screen snapshot.By virtue of
TreeSet
checking a Comparable
before trying to determine if the TreeSet
contains that object, we get a runtime exception and message saying that this makes no sense.Dealing with the Collection.contains(Object) Issue
In this post, I have demonstrated how the Collection.contains(Object) method (including the Map.containsKey(Object) method) can behave differently depending on how the particular collection implementation decides to throw or not throw the option
ClassCastException
when a provided object is not of a relevant class to the objects stored in that collection. In the many cases where the ClassCastException
is not thrown, this can be a potential error that is difficult to track down. There are some ways to reduce this possibility.1. Careful coding and code reviews might lead to developers or reviewers seeing that an object of an irrelevant class is being passed to the
contains
method. I'm typically uncomfortable leaving it with just this form of protection.2. Use of a modern version of NetBeans or of similar tools that flag the "suspicious" behavior can be very helpful.
3. Unit tests combined with code coverage tests can be helpful in identifying seemingly strange flows through the code that can be attributed to issues such as the one described in this post.
4. Collection implementations that do throw
ClassCastException
at least provide a runtime error to let developers know that an improper action is being taken. It may not be as effective as compile-time detection, but it does make it much easier to identify that there is a problem and what it is when it happens then without it. However, the major disadvantage with this approach is that the choice of which collection implementation to use is often driven by important considerations that often outweigh the desire to have runtime detection of an errant call to contains
.5. Logging the results of calls to
contains
(both the positive and negative results) can be helpful, but can clutter the code and the log files.6. Debugging is an obvious way to detect this problem, but is often more time-consuming than some of the approaches addressed earlier.
I generally prefer the first four steps over the last two because the first three steps are more preventative than the last two and the fourth step gives a clear indication of a problem when it occurs.
Saturday, April 3, 2010
Ten Best Chapters of Software Development Books
I have always had a fondness for books, both technical and non-technical. However, it seems to get increasingly difficult to read complete books. I have found that instead of reading entire books on software development, I tend to read specific chapters and portions that seem most relevant, useful, and interesting to me. In this blog post, I look at the ten chapters of software development books that I have found to be most influential or most impressive in my career so far as a software developer. These are chapters that I can enthusiastically recommend to any software developer who either wants to learn the subject of that chapter or who just wants to see what an especially well-written chapter can do.
Before getting into the chapters, it is worth noting that some of my favorite technical books are not included on this list. This is often because the entire book is great and it is difficult to isolate any one chapter that stands out from the others. For a list of great software development books, see Top 100 Best Software Engineering Books, Ever. Many of the books on this list are simply outstanding and I also like many of the "Cookbook" and "Recipes" books for practical learning, but these tend not to be the types of books with chapters on this list.
Without further adieu, here are (as of today) the ten chapters (in no particular order) that come to mind as the most impressive or influential for me from the software development books.
10. Learning Perl (Perl)
I have never cared much for Perl, but I have needed to use it off and on over the years. Because I don't use Perl regularly, I often don't remember much about it from the last time I used it. However, I have found that the first chapter of Learning Perl (Schwartz...) often provides what I need to quickly get back into writing Perl. This chapter is everything a person learning a new programming language would want in an initial chapter: a gentle introductory approach to several commonly used features of the language.
9. Java Enterprise in a Nutshell (Java)
Chapter 6 ("JNDI") of Java Enterprise in a Nutshell makes my list because it's an example of the definitive chapter on a particular subject. When I was trying to learn how to use and apply JNDI, it was difficult to find satisfactory resources. This chapter was very welcome. This is perhaps the least interesting chapter to a general audience on this list given its narrow focus and the somewhat abstracted or diminished use of JNDI in Java EE, but the chapter is representative of software development chapters on a specific subject that are the authoritative source of information on that subject.
8. Effective XML: 50 Specific Ways to Improve Your XML (XML)
The chapter from Effective XML (Elliotte Rusty Harold) making this list is not even a chapter! My favorite part of this book is its Introduction, which provides through but concise descriptions of XML terminology. This was especially important at the time of its writing to enable more precise terminology when talking about XML. I learned more from this introduction, which is available online, than I have learned from entire chapters of some XML books I have read.
7. RESTful Java with JAX-RS (Java/REST)
There are many resources (no pun intended) on Representational State Transfer (REST) that provide a high-level overview of what REST is. Chapter 2 ("Designing RESTful Services") of RESTful Java with JAX-RS (Bill Burke) goes well beyond introduction and leads the reader through the process of designing a REST-based application in a highly approachable manner. I cannot think of a better high-level description of how to apply REST (versus simply covering what REST is) than this chapter. Whether or not a developer wishes to use JAX-RS in his or her REST applications, this chapter is a useful way to begin really understanding how to design with REST.
6. Holub on Patterns: Learning Design Patterns by Looking at Code (Design Patterns)
I purchased the book Holub on Patterns: Learning Design Patterns by Looking at Code after attending Holub's presentation at SD West 2005. The book is every bit as opinionated (perhaps more so!) than Holub is in his presentations. That's why I like it so much! I especially like the first two chapters of this book. Chapter 1 ("Preliminaries: OO and Design Patterns 101") provides background information on object-oriented design and what patterns are and how to use them. This may sound basic and like nothing new, but I found many things in hear that made me re-think what I thought I knew. Primarily, I have come to agree with Holub that indiscriminate use of getters and setters is evil. Chapter 2 ("Programming with Interfaces, and a Few Creational Patterns") is also very good and covers why Holub asserts that extends is evil.
Holub believes strongly in his opinions based on a lengthy career. Although I don't always agree with everything he says and writes at the same level he does, I generally see the wisdom of what he is saying and apply similar principles (though to a less extreme degree) in my own development efforts. I like to read things that challenge my "old way of thinking," especially when they motivate improved ways of thinking and these couple chapters (with the SD West presentation) did just that.
Perhaps the most significant realization for me after reading these chapters is that objects are about the methods/behaviors and not really about the data. I had been taught that objects were structs with behaviors and this really was backwards. I especially like, and strive to follow, Holub's "prime directive" of object-oriented systems, which he defines as:
The get/set paradigm is really just about providing information. When an object goes way beyond providing information and does what the client needs, the need for senseless get/set methods is greatly reduced or eliminated. Specialized knowledge of how to act upon the data does not need to be in each client, but can be packaged with the data being acted upon.
5. The Practice of Programming (General Development)
The oft-cited Kernighan and Pike software development book The Practice of Programming is well-known for packing numerous useful observations from practical software development experience into a relatively (deceivingly) small book. For the software developer in a hurry, the "Epilogue" and "Appendix: Collected Rules" are especially useful. Because the "Epilogue" is only two pages and the "Appendix: Collected Rules" is only three pages, I feel like I can bend my own rules here a little and consider them together as a single chapter.
The "Epilogue" of The Practice of Programming summarizes in succinct text the underlying themes of the book such as simplicity, clarity, and interfaces. Each of the themes is summarized in a short paragraph and each theme is covered in detail earlier in the book with a chapter devoted to each theme. The "Appendix: Collected Rules" breaks down the "rules" of good software development covered in the book on a per-chapter basis as well. Between the Epilogue and this Appendix, one can quickly remember what he or she has read or can direct himself or herself to chapters in which he or she wants to learn more about that theme or set of rules.
4. Effective Java (Java)
I could have chosen several chapters from classic Java book Effective Java (Joshua Bloch) and could have filled this top ten with chapters from that book. However, the one chapter that particularly stands out to me from this information-rich book is Chapter 9 (Second Edition) on exception handling in Java.
There are many reasons for choosing this chapter in particular. One, exception handling is something that all Java developers need to understand. Two, exception handling is most effective when those throwing the exceptions and those catching and handling the exceptions use exceptions the same way. There have been numerous articles of using Java exceptions effectively, but a team that follows the principles of this chapter will need very little outside information on how to effectively apply Java exceptions.
3. Design Patterns: Elements of Reusable Object-Oriented Software (Object-Oriented Design)
For good and for bad, design patterns have left a major imprint on the software development literature. The term and its popularity started with the classic reference Design Patterns: Elements of Reusable Object-Oriented Software (Gamma, Helm, Johnson, and Vlissides). This book was the first major work to organize a library of design patterns and established a common "pattern" for other patterns books to following (pattern name, problem, solution, and consequences).
Although the "Design Patterns" book obviously applied a name to the concept of using recurring design strategies to solve different problems, the real value it had for me as I was learning object-oriented development concepts was its introduction to the key principles behind most of the cataloged design patterns. The first chapter of this book outlines at a high-level what a design pattern is, but its real value for me was when I read the portion of this chapter regarding interfaces ("interfaces are fundamental in object-oriented systems. Objects are only known through their interfaces."), Class versus Interface Inheritance, Programming to an Interface, not an Implementation, Inheritance versus Composition, and many other fundamental principles of object-oriented design and development that still seem to be ignored today.
2. Expert One-on-One J2EE Design and Development (Java EE)
It was difficult to select just one chapter from Expert One-on-One J2EE Design and Development that marked a change in how enterprise Java applications are implemented and brought what would become the Spring Framework to the attention of the world. One of the things this book did for me was to help me realize that I wasn't mistaken or missing something because I thought J2EE was more difficult than it was worth. Between this book and the 101 EJB Damnations, it was a relief to see my concerns more eloquently articulated. As I read Johnson's book, I recognized that many of the observations he was making were similar to what I had made. This made me even more interested in the topics that he covered in which I had less experience.
It was difficult to decide which chapter to feature in this list, but I initially limited it down to a choice between Chapter 4 ("Design Techniques and Coding Standards for J2EE Projects") and Chapter 6 ("Applying J2EE Technologies"). The latter, Chapter 6, features sections on deciding when to use and not use EJB and when to use and not use XML. However, I think if I have to choose, Chapter 4 is the one I will officially include in this list. That chapter is essentially an "Effective J2EE" chapter and is tremendous whether one is using Spring or not. In fact, this seemingly narrowly focused book has tons of generally useful discussion on building distributed and enterprise systems regardless of language or platform.
1. Software Craftsmanship: The New Imperative (General Development)
The book Software Craftsmanship: The New Imperative is a relatively short book and an easy read. However, I believe the general principles of software craftsmanship outlined in this book are what all developers serious about their careers should aspire to. The book also talks about how users and managers can contribute to the craft of software development, but I prefer the advice for developers because that is the area over which I have the most control. As with most of the chapters on this list, it is difficult to select just one chapter from this book. However, the very short (4 pages) Chapter 5 ("Putting People Back into Software Development") serves this purpose. In this chapter, author Pete McBreen issues this "call to arms":
Conclusion
This post has contained a list of ten chapters from software development books that have taught me new concepts, have inspired me to change my software development habits, or have otherwise significantly influenced me. In some cases, the chapters were simply so well-written that I wished I had written them. If I was to come up with this list on a different day without knowledge of having made this list, it is likely that I might have at least a couple differences.
What Chapter Has Impacted You Most?
What software development chapters have you found to be particularly well-written or have had a significant effect on how you develop software?
Before getting into the chapters, it is worth noting that some of my favorite technical books are not included on this list. This is often because the entire book is great and it is difficult to isolate any one chapter that stands out from the others. For a list of great software development books, see Top 100 Best Software Engineering Books, Ever. Many of the books on this list are simply outstanding and I also like many of the "Cookbook" and "Recipes" books for practical learning, but these tend not to be the types of books with chapters on this list.
Without further adieu, here are (as of today) the ten chapters (in no particular order) that come to mind as the most impressive or influential for me from the software development books.
10. Learning Perl (Perl)
I have never cared much for Perl, but I have needed to use it off and on over the years. Because I don't use Perl regularly, I often don't remember much about it from the last time I used it. However, I have found that the first chapter of Learning Perl (Schwartz...) often provides what I need to quickly get back into writing Perl. This chapter is everything a person learning a new programming language would want in an initial chapter: a gentle introductory approach to several commonly used features of the language.
9. Java Enterprise in a Nutshell (Java)
Chapter 6 ("JNDI") of Java Enterprise in a Nutshell makes my list because it's an example of the definitive chapter on a particular subject. When I was trying to learn how to use and apply JNDI, it was difficult to find satisfactory resources. This chapter was very welcome. This is perhaps the least interesting chapter to a general audience on this list given its narrow focus and the somewhat abstracted or diminished use of JNDI in Java EE, but the chapter is representative of software development chapters on a specific subject that are the authoritative source of information on that subject.
8. Effective XML: 50 Specific Ways to Improve Your XML (XML)
The chapter from Effective XML (Elliotte Rusty Harold) making this list is not even a chapter! My favorite part of this book is its Introduction, which provides through but concise descriptions of XML terminology. This was especially important at the time of its writing to enable more precise terminology when talking about XML. I learned more from this introduction, which is available online, than I have learned from entire chapters of some XML books I have read.
7. RESTful Java with JAX-RS (Java/REST)
There are many resources (no pun intended) on Representational State Transfer (REST) that provide a high-level overview of what REST is. Chapter 2 ("Designing RESTful Services") of RESTful Java with JAX-RS (Bill Burke) goes well beyond introduction and leads the reader through the process of designing a REST-based application in a highly approachable manner. I cannot think of a better high-level description of how to apply REST (versus simply covering what REST is) than this chapter. Whether or not a developer wishes to use JAX-RS in his or her REST applications, this chapter is a useful way to begin really understanding how to design with REST.
6. Holub on Patterns: Learning Design Patterns by Looking at Code (Design Patterns)
I purchased the book Holub on Patterns: Learning Design Patterns by Looking at Code after attending Holub's presentation at SD West 2005. The book is every bit as opinionated (perhaps more so!) than Holub is in his presentations. That's why I like it so much! I especially like the first two chapters of this book. Chapter 1 ("Preliminaries: OO and Design Patterns 101") provides background information on object-oriented design and what patterns are and how to use them. This may sound basic and like nothing new, but I found many things in hear that made me re-think what I thought I knew. Primarily, I have come to agree with Holub that indiscriminate use of getters and setters is evil. Chapter 2 ("Programming with Interfaces, and a Few Creational Patterns") is also very good and covers why Holub asserts that extends is evil.
Holub believes strongly in his opinions based on a lengthy career. Although I don't always agree with everything he says and writes at the same level he does, I generally see the wisdom of what he is saying and apply similar principles (though to a less extreme degree) in my own development efforts. I like to read things that challenge my "old way of thinking," especially when they motivate improved ways of thinking and these couple chapters (with the SD West presentation) did just that.
Perhaps the most significant realization for me after reading these chapters is that objects are about the methods/behaviors and not really about the data. I had been taught that objects were structs with behaviors and this really was backwards. I especially like, and strive to follow, Holub's "prime directive" of object-oriented systems, which he defines as:
Never ask an object for information that you need to do something; rather, ask the object that has the information to do the work for you.
The get/set paradigm is really just about providing information. When an object goes way beyond providing information and does what the client needs, the need for senseless get/set methods is greatly reduced or eliminated. Specialized knowledge of how to act upon the data does not need to be in each client, but can be packaged with the data being acted upon.
5. The Practice of Programming (General Development)
The oft-cited Kernighan and Pike software development book The Practice of Programming is well-known for packing numerous useful observations from practical software development experience into a relatively (deceivingly) small book. For the software developer in a hurry, the "Epilogue" and "Appendix: Collected Rules" are especially useful. Because the "Epilogue" is only two pages and the "Appendix: Collected Rules" is only three pages, I feel like I can bend my own rules here a little and consider them together as a single chapter.
The "Epilogue" of The Practice of Programming summarizes in succinct text the underlying themes of the book such as simplicity, clarity, and interfaces. Each of the themes is summarized in a short paragraph and each theme is covered in detail earlier in the book with a chapter devoted to each theme. The "Appendix: Collected Rules" breaks down the "rules" of good software development covered in the book on a per-chapter basis as well. Between the Epilogue and this Appendix, one can quickly remember what he or she has read or can direct himself or herself to chapters in which he or she wants to learn more about that theme or set of rules.
4. Effective Java (Java)
I could have chosen several chapters from classic Java book Effective Java (Joshua Bloch) and could have filled this top ten with chapters from that book. However, the one chapter that particularly stands out to me from this information-rich book is Chapter 9 (Second Edition) on exception handling in Java.
There are many reasons for choosing this chapter in particular. One, exception handling is something that all Java developers need to understand. Two, exception handling is most effective when those throwing the exceptions and those catching and handling the exceptions use exceptions the same way. There have been numerous articles of using Java exceptions effectively, but a team that follows the principles of this chapter will need very little outside information on how to effectively apply Java exceptions.
3. Design Patterns: Elements of Reusable Object-Oriented Software (Object-Oriented Design)
For good and for bad, design patterns have left a major imprint on the software development literature. The term and its popularity started with the classic reference Design Patterns: Elements of Reusable Object-Oriented Software (Gamma, Helm, Johnson, and Vlissides). This book was the first major work to organize a library of design patterns and established a common "pattern" for other patterns books to following (pattern name, problem, solution, and consequences).
Although the "Design Patterns" book obviously applied a name to the concept of using recurring design strategies to solve different problems, the real value it had for me as I was learning object-oriented development concepts was its introduction to the key principles behind most of the cataloged design patterns. The first chapter of this book outlines at a high-level what a design pattern is, but its real value for me was when I read the portion of this chapter regarding interfaces ("interfaces are fundamental in object-oriented systems. Objects are only known through their interfaces."), Class versus Interface Inheritance, Programming to an Interface, not an Implementation, Inheritance versus Composition, and many other fundamental principles of object-oriented design and development that still seem to be ignored today.
2. Expert One-on-One J2EE Design and Development (Java EE)
It was difficult to select just one chapter from Expert One-on-One J2EE Design and Development that marked a change in how enterprise Java applications are implemented and brought what would become the Spring Framework to the attention of the world. One of the things this book did for me was to help me realize that I wasn't mistaken or missing something because I thought J2EE was more difficult than it was worth. Between this book and the 101 EJB Damnations, it was a relief to see my concerns more eloquently articulated. As I read Johnson's book, I recognized that many of the observations he was making were similar to what I had made. This made me even more interested in the topics that he covered in which I had less experience.
It was difficult to decide which chapter to feature in this list, but I initially limited it down to a choice between Chapter 4 ("Design Techniques and Coding Standards for J2EE Projects") and Chapter 6 ("Applying J2EE Technologies"). The latter, Chapter 6, features sections on deciding when to use and not use EJB and when to use and not use XML. However, I think if I have to choose, Chapter 4 is the one I will officially include in this list. That chapter is essentially an "Effective J2EE" chapter and is tremendous whether one is using Spring or not. In fact, this seemingly narrowly focused book has tons of generally useful discussion on building distributed and enterprise systems regardless of language or platform.
1. Software Craftsmanship: The New Imperative (General Development)
The book Software Craftsmanship: The New Imperative is a relatively short book and an easy read. However, I believe the general principles of software craftsmanship outlined in this book are what all developers serious about their careers should aspire to. The book also talks about how users and managers can contribute to the craft of software development, but I prefer the advice for developers because that is the area over which I have the most control. As with most of the chapters on this list, it is difficult to select just one chapter from this book. However, the very short (4 pages) Chapter 5 ("Putting People Back into Software Development") serves this purpose. In this chapter, author Pete McBreen issues this "call to arms":
We must insist that developers really know their craft before we trust them to create systems for us or with us.
Conclusion
This post has contained a list of ten chapters from software development books that have taught me new concepts, have inspired me to change my software development habits, or have otherwise significantly influenced me. In some cases, the chapters were simply so well-written that I wished I had written them. If I was to come up with this list on a different day without knowledge of having made this list, it is likely that I might have at least a couple differences.
What Chapter Has Impacted You Most?
What software development chapters have you found to be particularly well-written or have had a significant effect on how you develop software?
Thursday, April 1, 2010
Dynamic Java Log Levels with JMX/LoggingMXBean, JConsole, VisualVM, and Groovy
One of the most popular uses of JMX is to change the logging level of an executing Java application that utilizes Java's built-in logging (java.util.logging). This is made possible by the platform MBean server and the built-in LoggingMXBean. As an example of this, consider the simple Java class below.
The Java Application that Logs
FickleLogging.java
The simple class above logs every five seconds at the INFO level. Because INFO-level logging is turned on by default, this simple application will log out the increments in each loop. JMX and the LoggingMXBean make it possible to change the logging level dynamically so that these info-level logs do not get written.
Adjusting the Logging Level via JConsole
The name of the logger in the above code is the class name or "dustin.examples.FickleLogging." If I don't realize this or remember it exactly, I can look it up because LoggingMXBean provides a getLoggerNames() method. This is shown in the next screen snapshot with JConsole.
One can click on "Values" field to have it expanded to see the available logger names. The next image shows this expanded view with the name of the logger from this simple application highlighted.
The highlighted logger name can be copied and pasted into the first String parameter field (p0) in JConsole for the setLoggerLevel method and the new level of logging can be placed in the second field (p1). This is demonstrated in the next screen snapshot.
Because the logging level of the regular logging updates in the simple example was at info level, setting the logging level to SEVERE means the logging in the running application stops. It only starts again if we set the logging level to INFO or more detailed level of logging. This is more dramatic in an actually running environment, but the next screen snapshot attempts to capture this by showing how the count in the log statements stops for a while and then starts up again.
The above image reflects the fact that I changed the log level via JConsole to SEVERE after the sixth logged message and then started it logging again by changing the level back to INFO in time for the tenth loop.
See Using JConsole to Monitor Applications for additional details on using JConsole to set the java.util.logging level of a Java application.
Adjusting the Logging Level via VisualVM
VisualVM is another graphical tool one can use to set the logging level of the simple application that I started this post with. Once the MBeans plug-in for VisualVM has been downloaded and installed (all easily done with its wizard), that tab can be used just like JConsole's MBeans tab was used above. The next two screen snapshots show use of the logger names attribute and the setLoggerNames operation. Note the similarities with JConsole.
Adjusting the Logging Level with Groovy Script
JConsole and VisualVM are easy-to-use graphical interfaces for interacting with a Java application's
To make the Java application accessible via remote client, I run it with the three system properties
You may notice in the image above that there is a skipped output between 8 and 12 in the count. This is thanks to the running of a simple Groovy script that uses JMX and is shown next.
I could have made this script more sophisticated with Groovy's built-in CLI support, but I use simply command-line args support here. The four necessary arguments after the script name are the Logger's name ("dustin.examples.FickleLogging"), the logging level to change to (such as SEVERE or INFO), the host of the application that's doing the logging ("localhost"), and the port via which JMX remote management is enabled (9999).
With the proper parameters passed to it, this Groovy script connects to the application started with remote JMX exposure and is able to adjust that application's logging level. More elaborate and sophisticated scripts could be written that could make it just a few keystrokes for a developer to quickly change the logging level of software as necessary.
Conclusion
Selecting the appropriate level of logging can be difficult both in terms of setting the level at which individual messages should be logged and in terms of what level of messages should actually be written out. The ability to adjust the level of logs that are written dynamically provides developers with more options in using logging for debugging and other purposes. In this post, I have attempted to demonstrate how easy it is to adjust the level at which logged messages are written by a particular Java application that logs with the built-in Java logging facilities. I have also demonstrated doing this with JConsole, VisualVM, and a Groovy script.
The Java Application that Logs
FickleLogging.java
package dustin.examples;
import java.util.logging.Logger;
import static java.lang.System.out;
public class FickleLogging
{
private static Logger LOGGER = Logger.getLogger(FickleLogging.class.getCanonicalName());
public static void main(final String[] arguments)
{
for (int i=0; i < 500; i++)
{
try
{
LOGGER.info("Sleep: " + i);
Thread.sleep(5000);
}
catch (InterruptedException threadException)
{
LOGGER.severe("InterruptedException: " + threadException.toString());
}
}
}
}
The simple class above logs every five seconds at the INFO level. Because INFO-level logging is turned on by default, this simple application will log out the increments in each loop. JMX and the LoggingMXBean make it possible to change the logging level dynamically so that these info-level logs do not get written.
Adjusting the Logging Level via JConsole
The name of the logger in the above code is the class name or "dustin.examples.FickleLogging." If I don't realize this or remember it exactly, I can look it up because LoggingMXBean provides a getLoggerNames() method. This is shown in the next screen snapshot with JConsole.
One can click on "Values" field to have it expanded to see the available logger names. The next image shows this expanded view with the name of the logger from this simple application highlighted.
The highlighted logger name can be copied and pasted into the first String parameter field (p0) in JConsole for the setLoggerLevel method and the new level of logging can be placed in the second field (p1). This is demonstrated in the next screen snapshot.
Because the logging level of the regular logging updates in the simple example was at info level, setting the logging level to SEVERE means the logging in the running application stops. It only starts again if we set the logging level to INFO or more detailed level of logging. This is more dramatic in an actually running environment, but the next screen snapshot attempts to capture this by showing how the count in the log statements stops for a while and then starts up again.
The above image reflects the fact that I changed the log level via JConsole to SEVERE after the sixth logged message and then started it logging again by changing the level back to INFO in time for the tenth loop.
See Using JConsole to Monitor Applications for additional details on using JConsole to set the java.util.logging level of a Java application.
Adjusting the Logging Level via VisualVM
VisualVM is another graphical tool one can use to set the logging level of the simple application that I started this post with. Once the MBeans plug-in for VisualVM has been downloaded and installed (all easily done with its wizard), that tab can be used just like JConsole's MBeans tab was used above. The next two screen snapshots show use of the logger names attribute and the setLoggerNames operation. Note the similarities with JConsole.
Adjusting the Logging Level with Groovy Script
JConsole and VisualVM are easy-to-use graphical interfaces for interacting with a Java application's
java.util.logging
-based logging level. However, there are times when one would prefer to change the log level of a running application via a command-line script. I demonstrate this next with a Groovy script.To make the Java application accessible via remote client, I run it with the three system properties
com.sun.management.jmxremote.port
, com.sun.management.jmxremote.authenticate=false
, andcom.sun.management.jmxremote.ssl
. These are set as shown in the next screen snapshot.You may notice in the image above that there is a skipped output between 8 and 12 in the count. This is thanks to the running of a simple Groovy script that uses JMX and is shown next.
#!/usr/bin/env groovy
//
// setLogLevel.groovy
//
// This script allows one to control the logging level of a Java application
// using java.util.logging that exposes its LoggingMXBean for management.
// Java applications using java.util.Logging may provide easily controlled logging
// levels even from remote clients.
//
if (!args)
{
println "Usage: setLogLevel loggerName logLevel host port"
System.exit(-1)
}
import javax.management.JMX
import javax.management.ObjectName
import javax.management.remote.JMXConnectorFactory
import javax.management.remote.JMXServiceURL
import java.util.logging.LoggingMXBean
import java.util.logging.LogManager
def serverUrl = "service:jmx:rmi:///jndi/rmi://${args[2]}:${args[3]}/jmxrmi"
def server = JMXConnectorFactory.connect(new JMXServiceURL(serverUrl)).MBeanServerConnection
def mbeanName = new ObjectName(LogManager.LOGGING_MXBEAN_NAME)
LoggingMXBean mxbeanProxy =
JMX.newMXBeanProxy(server, mbeanName, LoggingMXBean.class);
mxbeanProxy.setLoggerLevel(args[0], args[1].toUpperCase())
I could have made this script more sophisticated with Groovy's built-in CLI support, but I use simply command-line args support here. The four necessary arguments after the script name are the Logger's name ("dustin.examples.FickleLogging"), the logging level to change to (such as SEVERE or INFO), the host of the application that's doing the logging ("localhost"), and the port via which JMX remote management is enabled (9999).
With the proper parameters passed to it, this Groovy script connects to the application started with remote JMX exposure and is able to adjust that application's logging level. More elaborate and sophisticated scripts could be written that could make it just a few keystrokes for a developer to quickly change the logging level of software as necessary.
Conclusion
Selecting the appropriate level of logging can be difficult both in terms of setting the level at which individual messages should be logged and in terms of what level of messages should actually be written out. The ability to adjust the level of logs that are written dynamically provides developers with more options in using logging for debugging and other purposes. In this post, I have attempted to demonstrate how easy it is to adjust the level at which logged messages are written by a particular Java application that logs with the built-in Java logging facilities. I have also demonstrated doing this with JConsole, VisualVM, and a Groovy script.
Subscribe to:
Posts (Atom)