Industrial Logic
![]() ![]() |
EJ Pilot Laptop Briefing System Design Audit
Refactoring needs to be done immediately to fix some of the more unpleasant behaviors within the system. Initial refactorings would best be carried out in conjunction with mentors, such as those available through Noblestar or Industrial Logic. The greatest challenge faced by the designers of the system involves the legacy BTRIEVE database. The existence of this database, and the large body of code used to access it, explains why so much C++ code was developed when Java is clearly a better choice for both building distributed systems and programming in objects. Since it could be well over a year or two before an Oracle database replaces the BTRIEVE database, the team is faced with the choice of continuing to interact with BTRIEVE in the old style (i.e. a non-SQL approach that involves looping through records one at a time) or wrapping BTRIEVE with an SQL layer, which would allow PLBS Java applications to treat the database as if it were relational (like ORACLE, for example). It is unclear at this time if it would pay to wrap BTRIEVE with an SQL layer. The task would involve relying on a third-party product, could require an upgrade to the existing version of BTRIEVE, and might involve developing code to make the whole thing function. In addition, existing bugs in the BTRIEVE database (such as problems with concurrency) would not go away, and could create problems within the SQL layer. Therefore, at this point, it would not be a good idea to pursue this path, even though it would be good to write SQL against the BTRIEVE database. On the other hand, we think that the C++ section of PLBS should not continue to grow. Java is a much better language to use for future development, and therefore all staff should learn how to develop in Java (see BUSINESS VALUES). It is possible that the C++ DataManager program could be gradually refactored to first run reliably, and second, run with reduced responsibilities that could be handled by what is now called SessionManager. PLBS was designed using CORBA, which we think was a good design decision. PLBS is a distributed, multi-lingual, multi-platform system that is required to interoperate with systems written in legacy languages. PLBS is therefore a perfect fit for CORBA. Inprise's VisiBroker CORBA implementation is also a good choice, because it is the industry-leading ORB (Object Request Broker), it comes in Java and C++ versions, it supports advanced distributed object architectures, and it includes Caffeine, a feature that allows Java programmers to serialize (i.e., pass by value) objects over a fast IIOP connection (see OBJECTS BY VALUE). VisiBroker also represents a possible replacement technology for MQSeries (see FAT-FREE CODE). We think that VisiBroker is a superior choice over Java's RMI (Remote Method Invocation) or other messaging products (such as Information Broker by Active Software), because it is a superior technology that was made to solve the problems inherent in the PLBS. We did not like the overuse of VisiBroker's OAD within the system. Having one SessionManager and one DataManager for every remote call into the PLBS is not a good use of memory, and while it may help to get around a threading issue, it does not represent a sound design. At a minimum, we would like to see a handful of SessionManager servers using object factories to talk with unshared DataManagers. This particular refactoring would take some time to implement (as we note in AGGRESSIVELY REFACTOR). Performance will continue to be an issue until the system goes through a thorough performance tuning. This will involve a good deal of refactorings (AGGRESSIVELY REFACTOR), which are best carried out with TEST SUITEs in place. A week or two of PAIR PROGRAMMING using mentors would produce the best performance tuning. There is currently no disciplined development and coding process in place. This is hurting the PLBS team. The team needs a process, even if it is minimal. It is possible that a process will improve the quality and reliability of the code being produced. Our Design Audit follows. The audit consists of three sections: Process, Technology, and People. We have provided 19 ideas that reflect PLBS problems and what we would propose to fix them. Each point has a name, which the team can begin to use in their vocabulary. The points may be skimmed quickly by first reading all text in the bold font. Each point contains two statements: the first states the problem; the second states the solution. In between these statements and after them, we provide details about each issue as it relates to the PLBS project. Please send all questions and feedback to joshua@industriallogic.com |   |
![]()
|
  |
  |
  |
  |
Test Suite. . . the software in question is needed in production ASAP. Yet the software itself is not ready for production.
. . . No one is completely comfortable with the existing body of code. While it solves a business need, it is a little too slow or unreliable to be considered production-ready. Yet the software is needed today. Test Suite helps provide a safety net that will enable a team to make incremental improvements.
The vast majority of software projects get into this position. Code is
traditionally developed without attention to testing code, which
programmers usually think of writing after they have completed writing the
real code. Such testing code is often never written at all or in great haste
toward the end of a project, when there is little time.
The problem with this approach is that it makes it far more difficult to
actively improve the production worthiness of software code, because there
is no measuring stick to know if, after a refactoring, the code is any better
or worse. Manual tests work only so well, and often don't catch important bugs.
Therefore:
Write a series of unit tests that confirm when the system is running correctly and when it is not. Automate these tests into a single suite that may be run without user input, at the touch of a button.
Once a series of tests has been written, a team can safely begin to AGGRESSIVELY REFACTOR. Such refactorings will incrementally improve performance, reliability, software architecture, and code quality, while helping team members incrementally become better software designers and developers.
Pair Programming. . . every team consists of people at different levels of experience who write code of varying quality. Pair Programming explains how to make teams grow while producing better-quality software.
Talented software developers know how to write good software, on time, within budget. Yet such developers are a rare breed and are difficult to retain. So how can a company produce good software without many highly experienced developers?
The vast majority of companies have this problem: They need highly experienced
developers who simply aren't available. In the Midwest, the problem is
particularly accute. Companies struggle to find the right people, and
either lose them to higher bidders when they do find them or don't find them at all.
The trick is to transform a team of decent, but not highly experienced,
developers into a powerful unit, capable of producing quality software.
In our experience, one the best ways to transform a team is by
increasing individual experience and educating people through a
process called Pair Programming. This process involves having two people write and
AGGRESSIVELY REFACTOR code together
at one computer.
This may seem as if it could waste a lot of time. But in fact
it does the opposite: It reduces the bugginess of code, improves code design
while it is being written, and generally leads to easier-to-maintain,
better-quality code.
When we mentor companies, we often do so by pairing up with their developers
to write or refactor code. When we leave, team members pair up with each
other, changing partners every week or two.
This has the added bonus of making every team member aware of
the many different areas of the system. Companies suffer tremendously
when a team member leaves and no one can pick up his or her work. Pair Programming
helps prevent this from happening by giving everyone a chance to work
on different areas of the system.
Therefore:
Develop code in pairs, working side by side on pieces of a system. Use mentors to jumpstart the pair programming process, and give all team members a chance to pair program with each other.
Pair programming works best when pairs are given (or select) small chunks of work to complete. If a pair can't complete the work chunk within two to three days, the work chunk is either too big, or another pair will need to take a look at it. . . .
Design Audit. . . once a team is up and running and producing software, the question is: Are they heading on the right path, or veering off course? Design Audit describes a simple way to help teams stay on track.
Software designs, development teams, and development processes often get into unhealthy states during the course of systems development. If these problems aren't diagnosed and corrected, the problems can undermine the success of a project.
People go to doctors for regular checks-up. But many software development
efforts go on for months and years without going in for a "doctor's visit,"
sometimes called a "process check" or "design audit."
Such audits can save a vast amount of time and headaches. Often, an
outsider can see things that team members just can't.
Audits that are done effectively will find the problems and offer up
solutions. Then, the trick is for the team to implement the suggested
solutions.
PAIR PROGRAMMING is a way of doing
design audits at a very low level, where team members help each other
find and fix problems.
Management of the PLBS has already seen the need for and obtained an audit.
Given the effectiveness of this audit (i.e., how much did it cost,
how much time and money and how many headaches did it save), management
may consider additional audits periodically throughout systems development.
It should be stated, however, that projects that get off to a good start
sometimes don't need audits. We have found that by judiciously using experts early
on in the development process, many of the problems that appear late in the
game may be avoided, thus reducing the need for audits.
Therefore:
Submit systems for periodic "check-ups" by experts who can quickly diagnose problems and suggest practical solutions.
It can also be a good idea to use diffent vendors for audits, provided they are all experts. |   |
|
  |
  |
Precision NamingIt is easier to maintain software that reads like English than software that is filled with codes or unintelligble names. Precision naming discusses the importance of spending time to come up with names that mean something to another reader of the code.
Software that is hard to understand is a liability: The code becomes difficult to maintain, thus making life difficult for programmers and bloating company maintenance budgets.
In a piece of code in the PLBS system, I came across the following number:
8640000. I had no idea what this number was. I inquired about the number
and discovered that it was being used to do some date arithmetic. There
is nothing wrong with using 8640000 in an algorithm, but it needs to be
called NUMBER_OF_SECONDS_IN_A_DAY, or whatever it happens to be, not some
"magic number."
I saw numerous examples of "magic" numbers scattered throughout the code.
In a call to a method like "getgreat" (which is badly named itself), a programmer passed
in the number 6. But what does 6 mean? It means nothing to someone who didn't write
the code or who isn't familiar with the getgreat method. Such code needs to
be made more intelligable, because while it is no big deal on a very small project,
as a system grows, there can be so many of these "magic numbers" that
it becomes very difficult to make sense of or maintain the code.
Therefore:
Choose intention-revealing, precise names for all of your variables, constants, methods names, and object names. If you find something that is badly named, refactor the code by making the name change.
Some of the worst names encountered during the audit were getClientStuff() and sendClientStuff() .
Fat-Free CodeGiven a large body of code, the challenge for any team member is to understand the code, in order to be able to maintain or enhance it. Yet what if there is code in the system that does nothing at all?
Code that serves no useful purpose whatsoever is a liability: It can complicate what should be simple, confuse and mislead others, and generally lead to unwanted behavior.
In the PBLS, I saw too much fat: code that might have been used
but had been abandoned and never removed. Such code made sections
of the PBLS programs seem more complex than they needed to be, and
contributed to code bloat.
In one case, I found a line of code that was actually left over from
a copy-and-paste operation. This line of code really served no purpose
whatsoever and could easily have contributed to a nasty, for-do loop bug.
Generally speaking, removing all non-essential code from a system
helps make a system easier to maintain, enhance, and debug, and this
should be an ongoing goal of all EJ programmers. This
is especially true of any code that has been checked into version control.
If code is no longer being used, it should be removed from the
latest release of the software as early as possible.
Therefore:
Remove all code as soon as it becomes non-essential.
One of DataManager's largest pieces of "fat" is the use of POOLS.
Pools of connections into the BTRIEVE database are not being used.
Yet a good part of the code used to implement this feature remains
in the DataManager code. It would be a good idea to refactor DataManager to remove
this unused code, making the program simpler to read and simpler to (eventually)
rewrite in Java and ORACLE.
PLBS currently relies on IBM's MQSeries to obtain data from the
Jeppesen system. From our analysis, MQSeries did not seem to be
a good choice for the following reasons:
Small Parameter Lists. . . PRECISION NAMING helped explain the importance of accurately naming variables and methods. Now we'll consider the importance of keeping parameter lists short.
It's difficult to understand methods that expect too many parameters: Programmers can focus on about five to seven parameters, beyond which their eyes glaze over and their comprehension declines.
Many of the methods within the PLBS expect too many parameters. This
makes the code difficult to read and understand. Generally speaking,
the coding pattern seems to rely heavily on passing in many paramters
to methods and then passing out many values through "out" paramters.
A much simpler and better way to program is to use structures and/or
objects to pass data in and get data out. Passing in and getting back
well-named (see PRECISION NAMING)
structures and/or objects is a much better coding practice, which
will make systems easier to understand, update, and maintain.
Therefore:
Keep parameter lists small. Use structures or objects to contain larger numbers of variables, and favor return results over "out" parameters or references.
When a method is well-named and when it expects a small, managable number of parameters, the method itself can still be too long (see SMALL METHODS). |   |
|
  |
  |
Small Methods. . . To complete making a method easy to understand, enhance, and maintain, make the method body small, delegating calls to other methods within the greater method.
Methods that are large (on the order of hundreds of lines of code) are difficult to understand, maintain, and enhance, and thus represent a liability within a system.
The vast majority of methods within the PLBS (both in C++ and Java) are
far too long.
The length of these methods contributes to a style of coding that harkens
back to the days of Fortran or COBOL. This is far from what represents
either well-structured or object-oriented code. And while it may be
relatively easy for a method's author to understand, debug, and extend such code,
it is typically difficult for other team members (current or new)
to manage.
Therefore:
Create methods that are small in size and easy to understand. Let such methods delegate calls to other, smaller methods that are responsible for their own pieces of work.
A good way to shorten the size of methods is to use GUARD CLAUSES. . . . |   |
|
  |
  |
Guard Clauses. . . how can you make complex, nested logic simpler? An excellent technique for simplifying code is to introduce guard clauses.
Nested condition logic--"if" statements within "if" statements within "if" statements--is hard to read, hard to maintain, and not a good way of writing code.
The PLBS code is rife with highly nested conditional logic.
Methods go on for pages and pages, with outer "if" statements
stretching for hundreds of lines. This is a poor style of
coding because it can be very easy to introduce bugs when
modifying or enhancing such code.
The solution to using nested conditional logic is to write
code that checks for certain conditions upfront, and if they
are not met, exits the caller from the method. These boolean
checks are called "guard clauses."
The code that kicks the caller out of the method can either throw
an exception or return an error code. It would be wise to
AGGRESSIVELY REFACTOR the PLBS's
highly nested condition logic into a series of guard clauses.
Therefore:
Replace all nested condition logic with guard clauses, to make the code simpler to follow and easier to enhance and maintain.
It is very helpful to have TEST SUITEs in place before refactoring nested logic into guard clauses. . . .
Decoupled Logging. . . writing out log files is a common task. Yet how can you program a system so that business logic and logging aren't tightly coupled?
Code that writes input or output to a log file is often mixed with business logic, complicating code unnecessarily.
The PLBS is required to log inputs and outputs to log files. Yet the
logging code is heavily mixed in with PLBS's business logic,
making routines more complex than they need to be, and more difficult to
modify when a change must be made to either logging or business logic.
In the SessionManager, log data is being written to an Oracle database,
which is a time-consuming activity that can easily fail when given an
unavailable database or a bad user name or password. Thus, a good deal of
error handling is also needed to ensure that logging is successful, and
this extra code contributes to code bloat within a method
(see SMALL METHODS).
To minimize complexity and method length, and to reduce coupling between
logging code and business logic, it is best to use object wrappers (known as Decorators)
for logging activities. In VisiBroker, this is best accomplished using either Smart Stubs,
Interceptors, or Object Wrappers, while in straight object-oriented code,
this is best implemented using the Decorator pattern (see Design Patterns, by Gamma, Helm, Johnson, & Vlissides).
Therefore:
Decouple logging code from business logic by wrapping business objects with logging objects that delegate calls to business objects. Logging wrappers may log data either before and/or after business logic calls.
Wrapping objects is a powerful way of adding behavior to a system without having to modify the system. DECOUPLED DEBUGGING is a way of using object decoration (or wrappering) to easily turn a system's debugging output on or off. . . .
Decoupled Debugging. . . It is difficult to debug a system that is multi-lingual or composed of distributed objects. Debug code helps but if it is tangled up with business logic it can slow down a system, make it harder to maintain, or be difficult to turn on or off. Decorators provide a way to simplify this process without sacrificing the need to write robust debugging code.
In distributed systems, debugging code may need to live within a running system, but it does not have to be mixed in with business logic.
Most of the debugging code in the PLBS is completely mixed in with business
logic. Combined with all of the logging code, the logic within
many methods has become overly complex. In addition, most of the debugging code
has no way of being turned on or off. Thus, all performance tests of the
system contain the time it takes to write out debugging output (a task
that can actually consume a lot of wasted time).
A simpler, better way of writing debugging code, especially for distributed
systems, is to use object decoration (i.e., the Decorator pattern).
In VisiBroker, one can use Smart Stubs, Interceptors, or Object Wrappers.
The decorators can be written such that a system can optionally turn on
or off debugging: This is best accomplished by creating object factories
that can return either "debug-wrapped" objects or "debug-free" objects.
Therefore:
Decouple debug code from business objects, such that the debug code may be added or removed at will, using object decoration.
. . . |   |
|
  |
  |
Objects By Value. . . when writing distributed systems, designers must decide when to pass client programs references to remote objects, raw data, or object passed by value.
Sending raw data from a remote object to a client is sometimes the best idea and sometimes the worst. When making the decision, designers must consider network time, marshalling and unmarshalling time, code complexity on the sending and receiving sides, and ultimately what will support the most efficient, extensible behavior.
In the PLBS, the SessionManager spends some time packaging up some
data to send to a remote client (on a Libretto), and when that data
gets to its destination, it then needs to be unpackaged before it
can be used by the remote client. In addition, the remote client
sends data to the SessionManager in the same fashion.
This looks like a rather inefficient way of spending time, given that
VisiBroker supports the passing of objects by value. When programs
pass objects by value, they do not have to spend time packaging up
data and unpackaging it: The programs simply use the objects as they
would any variable.
This is highly advantageous since it means that the object's data does
not have to be exposed (i.e., it can remain encapsulated, and thus may
change its format at a later date, with no side effects on either client
or server).
Therefore:
Send and receive objects by value when it is faster and easier to do so.
iSessionManagerImpl's getClientStuff
method may be refactored to return the contents of dataObject, instead of relying on
a byte_arrayHolder and an "out" parameter. The simplest way to implement
the refactoring will be to reverse the return parameter with an "out" parameter
(i.e., instead of returning an int, make the int an "out" parameter and
let the method return the contents of dataObject). The new method signiture
would look as follows:
public Vector getClientStuff(IntHolder returnCode)
In the current method, dataObject is filled
with the contents of a variable named vectorToSend.
In the refactored method, the return result of the method can simply be
vectorToSend.
Please note that we generally prefer using CORBA exceptions vs. passing
int return results, but that would involve a larger refactoring. In addition,
the method name getClientStuff should
itself be refactored to express what is really passed back to the Libretto
(see PRECISION NAMING).
|   |
|
  |
  |
Optimized Java. . . there are optimized ways of using any language. But many textbooks and most courses rarely cover optimization techniques.
Many of the most basic and widely used Java objects and language elements are extremely inefficient and ought to be removed from any program that needs to execute with speed.
Most good designers and programmers know that it is best to optimize
a system after designing and writing the system. Profilers are great
tools for finding inefficiencies, and they can often point developers
into areas of code that they would not expect to be bottlenecks.
Profiling Java code on a number of projects has revealed the slowness
of a number of the JDK's objects (such as Vector and Hashtable) and
language elements (such as "instanceof").
If one studies how code is implemented in the JDK itself, one will
learn many coding optimization tricks.
To ensure that the PLBS runs as quickly as possible, it will be best
to use a good profiling tool and to replace the inefficient Java
code with more optimized Java code.
Therefore:
Frequently use a profiling tool to find and fix inefficient areas of a system. Learn which features and elements of a language are inherently slow, and begin to avoid using them when writing time-critical code.
We've used two Java profiling tools that you may consider using:
JProbe
by KL Group (800-663-4723),
priced at approximately $499/developer, and
OptimizeIt! 3.0 Professional by Intuitive
Systems (408-245-8540), priced at approximately $389. Both of these
products will take you a little time to learn, but will be useful
over the long haul. The two products tied for a "Best of Show" award
at a recent SIGS Conference for Java Development.
We'd also suggest that you use Java arrays in place
of Vectors whenever possible.
We have found arrays to be much faster
than Vectors (the JDK itself uses arrays under the hood).
In addition, instead of using Java's slow instanceof
operator (for an example, see SessionManager's sendClientStuff() method),
it will be faster to use either codes to identify your classes, or
to use polymorphism (in which case you won't care about the identity of your classes).
PLBS's Libretto client is currently quite slow to start and execute.
It uses the Java Foundation Classes (a.k.a. Swing), which is a very good
framework, but still not entirely fast (you can write
efficient Swing code, but it takes a lot of time).
We would suggest a quick rewrite of the Swing-based client
program, using Java's faster, simpler AWT (Abstract Windowing Toolkit).
And contrary to what we heard from PLBS programmers during the audit, it is
possible to implement "hot-keys" (or keys that the pilots may type to move
from screen to screen, instead of using the mouse).
Since the current user interface of the
Libretto client is neither complex nor sophisticated,
it should not take more than a few days to migrate the Swing-based code
to AWT.
Software Updates. . . code changes over time, and it is often necessary to redistribute newer versions of code. Everyone wants this to be a painless process, so many products advertise that they do code updates painlessly. But are these tools necessary or is it relative easy to write a code distribution process for a distributed system?
Distributing newer versions of code can be relatively easy to implement. And yet companies often think they need to buy expensive software to manage this process.
The PLBS has a definite requirement to remotely update code on the
Librettos when PLBS client code changes. The team has purchased a
product called Backweb which may or may not make this a painless
process.
Yet it is unclear that a tool is needed at all. Since the Libretto
program that is running is a fully fledged Java application, it is not
subject to certain sand-box limitations, which would prevent a program
from writing to a hard drive. It is therefore quite possible that the
system could update itself by simply downloading a JAR file and writing
it to the appropriate directory.
Therefore:
Carefully consider how to perform software updates without a tool before deciding to introduce yet another tool into a system's administrative process.
. . .
Business Values. . . software systems are developed to solve business objectives. Inidividual preferences, such as languages, methodologies, or products, must be subordinate to these objectives.
If individuals pursue their own agendas in the implementation of systems, business objectives may be compromised. Technologies are often chosen not because they are the best solution but because of individual comfort levels with that technology.
Java is a simpler, cleaner, lanaguage than C++ that allows developers
to be more productive. The Java community is expanding rapidly while
C++ is in decline. Using C++ because of someone's personal comfort level
will leave the enterprise with a more complex, harder-to-maintain,
more expensive system.
This is an example of a business value. Given EJ's existing
code base, the quickest migration path towards Java is best for the enterprise.
Given that PLBS already contains C++ code, it is clear that the
quicker this code is refactored to Java code, the better off EJ will be.
To be highly effective business technologists, team members within
EJ's staff must make their own preferences secondary to
more important business objectives.
Therefore:
Keep the overall business objectives uppermost in consciousness when choosing technological solutions.
. . .
Simplicity First. . . it is difficult to create simple, easy-to-use software. The majority of designers and programmers overcomplicate software, and this added complexity has a high cost for an enterprise.
Technologists often overengineer software, making it more complex and flexible than it needs to be.
Simplicity does not seem to one of the overarching
BUSINESS VALUES of the PLBS team.
Much time, energy and money can be wasted in the effort to create flexible
software. Yet often a large percentage of the built-in flexibility
is never used, thus contributes to the "fat" within
a system (see FAT-FREE CODE).
Simplicity is not a license to hack. It is a challenge to write software
that is simple, functional, and easy to enhance. The PLBS team would
do well to aim for simplicity, as it continues to design, debug and
refactor the system.
Therefore:
Design and write software that solves business objectives in a simple way, without introducing unecessary flexibility.
. . .
Egoless Debugging. . . bugs can be extremely difficult to resolve. It's often more efficient to let another programmer attempt to debug some code than it is to struggle for days by onself.
When a programmer gets rooted down by a bug, he or she can waste an inordinate amount of time trying to find a solution. This effort can rob a project of valuable time and reduce the programmer's productivity to a crawl.
Bugs can often be resolved more efficiently when programmers
remove themselves from the situation, and invite others to have a
look.
It is often a challenge to do this, as it can involve suspending
one's ego in favor of getting to a solution faster.
The PLBS team is already doing this, to an extent. The recent C++ memory
bug was resolved relatively easily when another pair of eyes looked
over the bug, offering a simpler solution to the problem.
Therefore:
When faced with difficult bugs, put aside one's ego and work with team members to find a solution.
. . .
Borderless Development. . . what is the best physical configuration for a development team?
Software developers are more productive when they work together than when they work alone. Yet it is commonplace for developers to work in cubicles and offices, isolated from their team.
Therefore:
Create borderless development environments in which developers may easily communicate and work together.
. . .
Information Sharing. . . when individuals make it a habit to share the knowledge that they acquire, the team itself may become more productive.
Developers often learn new techniques from books, magazines, friends or mentors. Yet often they fail to share their new found knowledge with the members of their team.
The PLBS team is working with languages and products that are
relatively new to them. Everyday individuals learn new things about
the various technologies. This is valuable information. If
it is shared with the rest of the team, the team itself can
become more productive.
Since the PLBS team does not currently work in a
BORDERLESS DEVELOPMENT
environment, they may best share information by using an email
list server or by posting notes on a shared wall.
It is very important to share ideas and techniques that
increase productivity or reduce complexity. Such ideas can
come from valuable training classes, PAIR PROGRAMMING
or a meeting with a mentor.
Therefore:
Remember the most valuable new knowledge you acquire and endeavor to share it with your team.
A pattern language is an excellent medium for sharing hard-won knowledge. |   |