Sunday, May 23, 2010

Lugaru - Part 5

In my previous post (Lugaru - Part 4) the first problem I identified was the role and responsibility of the Weapons class. Namely, a single instance of Weapons represented 30 weapons when it should only represent one. The refactoring exercise to correct this started off as a philosophical one, but ended up being VERY beneficial. I'm very happy I did this before tackling the implementation details of Weapons. Here are the major steps I took.
  1. Renamed Weapons to Weapon.
  2. Globals.cpp holds the global instantion of Weapons. It went from this:
Weapons weapons;
to this:
const unsigned int max_weaponinstances = 20;
Weapon weapons[max_weaponinstances];
unsigned int numweapons;
The max_weaponinstances constant comes from Weapons.h.
  1. With max_weaponinstances moved out of Weapons.h, all of the Weapons' attributes that were arrays become single instances. This is because the class is no longer maintaining the state of multiple weapons, just one. There were 44 places this had to be done.
  2. The Weapons class has four class operations - a constructor, destructor, and two others. One called "doStuff" that updates the game state of all the weapons. The other is called "draw" which pushes the weapon illustration down the graphics rendering pipeline. At the top of these two operations are for-loops that iterate over all the weapons in use (stored in numweapons). Those are gone. Instead, they now appear in GameDraw.cpp.
    There were 823 instances of referencing the loop counters in Weapon.cpp
  3. Classes that are dependent on weapons have a slightly different syntax. As an example, in Person.cpp it used to read:
  4. if(weapons.type[weaponids[weaponactive]]==knife) ...
    but now reads:
    if(weapons[weaponids[weaponactive]].type==knife) ...
    I find the new way much more readable since it puts the weapon attribute closed to the thing its being compared to.
  5. Weapons used to contain texture information. In the original design, these would only be registered with OpenGL once. In the new design I didn't want 50 weapons registering the same textures 50 times. To get around this, I made the texture id's global, (*GASP*). I rationalized that refactoring is about going from bad to better, and this let me move on quickly. I have no intention of leaving it this way long term.
Overall, I think this left the code MUCH more readable and improved some design aspects of Lugaru weapons. Next up will be getting some unstanding on the Weapons implementation.

Tuesday, May 18, 2010

Lugaru - Part 4.1

As a quick addendum to Part 4, I realize that enumerations can't be used for every constant since enum values can only be integer types. The gravity constant example I used in the previous post would need to be represented as a const float.

Also, for further reading on this, I'd point you to Scott Meyer's "Effective C++ Third Edition" Item 2, "Prefer consts, enums, and inlines to #defines"

And for you eager beavers that want to start reading on the polymorphic weapon heirarchy, check out "Refactoring - Improving the Design of Existing Code" by Martin Fowler. Specifically, the section on Switch Statements in chapter 3 "Bad Smells in Code" and "Replace Type Code with Subclasses" in Chapter 8.

Monday, May 17, 2010

Lugaru - Part 4

I'm excited to announce that by my fourth post on Lugaru, I have doubled my subscribers! Now in addition to my sister, I have the creator of Lugaru following. Hopefully my mother will subscribe and triple my readership in a single month...

Well, after spending a weekend tinkering with the code, I finally have a running version of Lugaru for Windows. Having a running application will allow me to do two things:
  1. Experience Lugaru as a player with the benefit of stopping the game any any point I choose to look at the game's state in code.
  2. Verify any changes I make don't cause unintended consequences in the game. Remember, refactoring is about improving the design of software without changing its outward functionality.
After poking around, I've fallen in love with the file containing the game's logic of weapons. In the CodeCity, the yellow building below is the "Weapons" class.



Why start with weapons? Well, think about how you can take a human being and decompose it into mind, body, and soul. You can then decompose the body into systems (nervous, digestive, circulatory, etc). Decompose circulatory to heart, arteries, veins... all the way down to the point of cells and molecules and atoms. Object Oriented programming does the same thing - takes a complex problem and breaks into into manageable pieces while also defining the interactions between those pieces. If you were going to redo the human body (lawl) you'd either want to start by identifying problems at the macro- or micro- level. "Weapons" to me is the micro of Lugaru.

Okay, so enough intro to programming. Let's dive in to Weapons...

Unfortunately, we break our necks because we hit the shallows! Let's talk about the file name "Weapons." It's named that because it contains the class "Weapons."
Design Smell #1: Class name is plural

A golden rule of OO programming is that classes represent a single representation of something. If you need multiples of that something, you instantiate multiple objects of that class. As it stands, a single instance of "Weapons" handles 30 weapon objects. When we get further into the code, we'll see the issues that creates.

Code Smell #1: Favoring #defines for constants

Some quantities in a game never change, for example the downward pull of gravity as 9.81. That quantity could be reproduced all over the game's files. If you want to change that to a more precise number or change it from metric to imperial units, however, you'd have to go hunting for every single place you typed "9.81". Heaven help you if you over-look a place! That's why you use a constant. A constant says, "I am defining this word to represent this quantity." You can then use that word all over your game, and if you need to change the quantity down the road, you only have to change it in one place, the word doesn't change!

In C++ there are multiple ways to create a constant. The Wolfire team chose to use #defines.
#define max_weapons 30
#define max_weaponinstances 20

#define knife 1
#define sword 2
#define staff 3
Using #defines for constants are not good for several reasons. First, a #define is a pre-processor directive. It's a very low-level command that literally replaces every instance of the token with the quantity before processing the file. If something about the quantity causing a compile error, there's no way for the compiler to acknowledge the #define macro. Let's pretend a clumsy programmer fat-fingered some extra characters into the knife macro:
#define knife 1dkf
The next time the program was compiled, he'd get a slew of errors (I got 145 when I tried it.) The first three errors I got were against the following line of code:
if(type==knife)
and the errors were:
  1. syntax error: 'bad suffix on number' [ed. I see no number here]
  2. syntax error: missing ')' before identifier 'dkf' [ed. I see no dkf here]
  3. syntax error: ')' [ed. I don't even know what this means!]
Yes, I realize the IDE you're developing in might give you some tips, but there are other problems with this approach. Second, #defines do not offer compile-time type checking - a big feature of C++. Since #defines are token replacements, the compiler has no problem with the following:
int weapon_type = max_weaponinstances;
weapon_type = rabbittype;
weapon_type = tracheotomy;
All of which are macros in Lugaru that are associated with integers, none of which are associated with weapon types. This, coincidentally is why using
static const int knife = 1;
Is better than a #define but still has problems. That's why I like enumerations. They allow you to group constants and get compile-time type checking. The weapon constants we saw above are re-written as:
enum Weapon_type
{
knife = 1,
sword = 2,
staff = 3
};

And that means the 'type' attribute in the Weapons class can go from an integer to a Weapon_type. That gives a reader a much clearer idea about what the valid values of 'type' are.

Of course, the penultimate solution is to replace these type codes with polymorphic classes, but refactoring is about going from 'bad' to 'better' one step at a time (as opposed to 'bad' to 'perfect' in one giant leap.) Are there better ways to define constants? What advantages do the alternatives have over my proposal?

Thursday, May 13, 2010

Lugaru - Part 3

Several months ago I was introduced to CodeCity by Richard Wettel. In his words:
CodeCity is an integrated environment for software analysis, in which software systems are visualized as interactive, navigable 3D cities. The classes are represented as buildings in the city, while the packages are depicted as the districts in which the buildings reside. The visible properties of the city artifacts depict a set of chosen software metrics, as in the polymetric views of CodeCrawler.
It's been a fun little program for visualizing applications. Here is the code city for Lugaru, (I added the class names to the two largest classes!)
The length and width of the buildings are scaled according to the number of attributes in the class. The height is scaled according to the number of operations. I don't have the application available to me as of this writing, but I seem to recall the Person class having ~250 attributes to give you a sense of scale.

So the question is where to start. Some would say start at the highest level class, some would say start with the 800-pound gorilla in the application (i.e. "Person").

According to the paper in my previous post, before any code changes start, I should have test cases in place to ensure that the behavior before the refactoring is the same afterward. How am I going to test this thing?! And before testing, I've got to get the thing running. I'm curious if it's working out of the box for anyone else. Wolfire IRC says no.

Lugaru - Part 2

So before I jump into Lugaru code, I feel like I need to make sure the foundation is laid for what I'm doing. I intend to refactor portions of Lugaru in order to increase the ease of extending it. For those unfamiliar with refactoring, here is a paper I wrote with Mark Ward for a class we took in the spring on OO Analysis and Design.

A Framework for Effective Refactoring
Abstract
Refactoring is a method of applying a series of small transformations to existing code—without affecting its external behavior—in order to make the code as a
whole more understandable and thus more maintainable and less error-prone. The decision to refactor can be controversial—especially to management—given that resources are being expended for reasons other than adding revenue-generating functionality. Our position is that periodic refactoring is an effective means to improve the long-term viability of a software system by ensuring that the code remains fresh and readily adaptable and extensible to changes in requirements or the addition of new functionality. As such, refactoring makes sense from both a technical and a managerial perspective. We briefly introduce the topic of refactoring and discuss opposing viewpoints, and we then propose a five-step framework defining the high-level process that we believe is essential to ensure successful refactoring. We conclude with two case studies to illustrate the application of the five-step framework.

1. Introduction

Software systems evolve over time as requirements change and new features are added. During this period of evolution, the quality of the code can degrade as changes are made that the original design may not have foreseen and thus may not accommodate cleanly. Other times changes are made under schedule pressure without time to consider design impacts on maintainability, extensibility, testability, and other quality considerations. Such code can become difficult to understand, increasing the likelihood of injecting defects during future updates. Even the most carefully-conceived initial system designs are susceptible to these problems as the system evolves in unforeseen ways. Just as the highest-quality cars require tune-ups, so too do the best software systems. The process for “software tune-ups” is known as “refactoring.”
Refactoring is a method of applying a series of small transformations to existing code—without affecting its external behavior—in order to improve the qualitative characteristics of the design. In doing so the code as a whole becomes more understandable and thus more maintainable and less error-prone.

2. Why Refactor?

Refactoring asserts that the design of a software system is not a single event [1]. An engineer that regularly refactors his code is continuously reassessing the design of the system and asking if it is still has acceptable characteristics of quality software (e.g. “Can I easily extend the system to include next month’s feature list?”). When software no longer has those levels of quality, it is wise to revisit the design of the software.
When software is not refactored, those classes with design issues become more change-prone [4]. As a result, they can create bottlenecks in development (since multiple developers will need to modify the same class) and the risk of error injection increases.

3. When to Refactor

Fowler [1] does not recommend scheduling time to refactor. It’s something you do “all the time in little bursts.” He also argues that “not having enough time” is usually a sign that you need to refactor; the reason you don’t have enough time is likely because the code is not as maintainable or extensible as it should be, causing your current effort to take longer than it should.
In addition to code smells, other indicators have been proposed to help drive the decision to refactor. One such indicator is change smells [3]. Change smells are not visible in the code, but are detected by analyzing change dependencies mined from the software’s change history to predict areas that would benefit from refactoring.
Another proposed indicator is architectural smells [6]. This technique is intended to complement existing techniques identified for addressing code smells by looking for “high-impact” (i.e., high benefit) architecture-related refactoring opportunities based on “architectural violations.”
The authors of Code Complete remind us that, “The number of refactorings that would be beneficial to any specific program is essentially infinite” [7]. Consider the following guidelines when deciding which refactorings are most important:
  • Modules that are difficult to read
  • Modules with complex conditional logic
  • Modules that are error-prone
  • High-complexity modules
4. When Not to Refactor
There are times, however, when refactoring is not a prudent course of action. One instance is when the software becomes so bug-ridden that it should be rewritten rather than reorganized. Fowler suggests working through this by encapsulating the components of the system and then examining each one to see if it needs to be rewritten.
Another inopportune moment for refactoring is near an impending deadline. Recall that the motivation for refactoring is to increase quality characteristics without changing functionality. It serves no purpose to do this right before the system is shipped. This isn’t to be confused with “not having time to refactor” which could be an indication that a lot of time and energy is being exerted to further a bad design.
Other situations that would prevent you from refactoring would be if the software is tied to components outside the engineer’s control. If the external interfaces are solidified with a sub-contractor or company data-base, the extent to which a refactoring can occur can be limited [8].

5. Opposing Viewpoints

Many references are made to refactoring being a controversial topic, yet there is surprisingly little formally-published literature offering a defense of opposing viewpoints – perhaps because most of the published work tends to be academic in nature, focusing on the technical merits rather than management considerations, where much of the controversy lies. Discussion of dissenting opinions can more readily be found in web-published articles and online discussion forums, however.
While not practical to address every criticism of refactoring that we found, we will briefly acknowledge several and present our viewpoint on the discussion.
  1. Managers believe developers should be adding features, not reworking old code [10]. It is easy to understand how refactoring can be a hard sell to management. A manager is concerned with trying to get their product delivered within schedule and budgetary constraints. Most projects overrun one or both of these constraints. Especially on a project that is projected to be late or over budget, refactoring can seem like a non-value-added task since time is being spent but no new revenue-generating features are being added to show for it. We have already discussed that there are times when refactoring may not be not appropriate, such as when a deadline is looming. However, for managers who frown on refactoring at any time in the project’s lifecycle, perhaps Martin Fowler offered the best suggestion: don’t even tell your manager that you are refactoring [1]. While certainly a controversial suggestion, his argument is that software developers are professionals who want to build effective software as rapidly as they can. Refactoring helps you develop faster, therefore it can just be integrated as part of the development process and not viewed as a separate activity for which you need to request specific approval from your management.
  2. Refactoring is nothing more than an act of “beautification” [9]. While in a sense this is partially true, the chosen term clearly implies a disdain toward refactoring. Similar – and harsher – criticisms suggest that refactoring is a “waste of resources and a get out of jail free card for incompetent developers” [11], and “basically a programmer’s ego trip and nothing else” [5]. Ouch. These criticisms seem to be based on the premise that “there is not much point doing afterwards what can be done from the start” [9]. We would argue that no one has suggested that code should initially be written poorly and later “beautified.” However, management pressures often force developers to release code as soon as it works rather than allowing time to optimize it “from the start” for understandability and maintainability [11]. Furthermore, ongoing updates to the code after the initial release are likely to have a deleterious effect over time—regardless of how well the code was initially written—that at some point must be dealt with through refactoring in order to keep the code understandable and maintainable.
  3. Any code change is an opportunity for error injection [9]. This is absolutely correct. However, the purpose of having a complete set of test cases that you run pre- and post-refactoring, and to refactor in small steps with testing between each step, is to mitigate the potential for undetected error injection to the point that it is almost negligible.
  4. Refactoring leads to considerable extra effort in adapting the unit test suite [9].
  5. Refactoring is nothing new – all the techniques have been around for years [8] [3]. This is largely true, but it is still immensely useful to give this set of best practices and techniques a name and catalogue them with examples and guidelines for when they are and are not applicable in order to efficiently disseminate this collective body of knowledge to developers.
While some refactoring techniques (e.g., moving a method from one class to another) will require at least some unit test rework, most do not. If you have a complete suite of unit tests already in place, which should be the case even if you are not planning to refactor, then the impact due to most refactorings is likely to be minimal. In some cases, however, this criticism is justified (see case study 2, section 7.2); however, we do not believe this is a valid across-the-board criticism.

6. Five-step Framework for Refactoring

To ensure refactoring has a methodical and intended outcome, the authors have created a five-step process for carrying out a refactoring exercise. It is very important to note that during a refactoring exercise, several design issues may need resolution. Follow these steps for each issue in series rather than trying to redo it all at once, that way if a step in the process changes the system’s behavior, the injected error will be easier to locate.
6.1. Identify the Need for Refactoring
No journey can begin without a charter and no refactoring can begin without a cause. As software engineers, we have all come across code that met the requirements and wasn’t “wrong”, but it certainly isn’t “right.” Kent Beck introduced the concept of code smells – any symptom in the source of a program that possibly indicates a problem. These code smells are often the first indication that a refactoring is in order. Martin Fowler [1] popularized the term and catalogued several code smells.
6.2. Establish Test Cases
If the goal of refactoring is to correct issues in software without altering its external behavior, test cases must be in place to ensure that the behavior of the system before the refactoring is the same afterwards.
Having self-verifying tests (i.e. tests that check the software’s output against expected or “truth” values) ensure that the engineer can execute the test suite rapidly and repeatedly.
6.3. Develop a Plan for Design Changes
Just as an engineer would never launch into coding without a design, an engineer engaged in refactoring should never begin without a plan. The plan should be a concrete list of steps to implement a desired change in the software. This plan is especially important when the engineer is attempting to increase the encapsulation of a class since the class’s data will be tightly coupled to the rest of the system.
6.4. Implement the Plan
With a plan in place, begin executing it in the order it was laid out. If an unforeseen problem arises, consider retreating back to Step 3 and see if the overall plan needs to be revised.
6.5. Re-run the Test Cases
Now that the refactoring is complete, the test cases that were established in Step 2 must be rerun. If the test cases still pass, the refactoring exercise is complete. If however they fail, the software must be reexamined.
Note that some forms of refactoring such as moving methods from one class to another will break the test environment. Van Deursen [2] observes that this ideal scenario cannot always be carried out without having to update the tests.

7. Case Studies

In this section, two case studies regarding refactoring are discussed. The first is a story of a theoretical software application that encounters a dead-end when its developers attempt to extended it. Refactoring allows the application to become easily extendable. The second case study involves a software application for an aircraft subsystem and discuses how the designers used refactoring to solve several issues they saw with the software.
7.1. Case Study 1: Burgertime!
A small fast-food store once had a software package that was designed by the store owner’s son. The software was responsible for tracking all the ingredients used by the store and their various information– purchase date, expiration date, quantity, price, etc. Given that the store only sold one thing – hamburgers – the number of ingredients was very small. The owner’s son, not knowing better and having no appreciation for the future, implemented all these as enumerations in a list, as shown:
enum Ingredients
{
buns = 0;
ground_beef = 1;
cheese = 2;
lettuce = 3;
pickles = 4;
mustard = 5;
onions = 6;
};
Referencing information on the ingredients was done in switch-statements where each ingredient was a case. In all, there were perhaps a dozen switch-statements in a dozen files throughout the application.
The software worked, and the business prospered to the point where the wanted to expand their menu – whole wheat buns, five kinds of cheese, and a complete dessert menu. Assuming this did well, there was even talk of breaking out and doing more than just hamburgers!
7.1.1. Step 1: Identify the Need for Refactoring.
The owner’s son was commissioned to update the software. Right away he knew there were problems. To add a single new item to the application, he had to reopen and modify each switch-statement. Even if he wanted to hire an additional programmer to help add ingredients to the software package, he couldn’t because they’d be constantly asking each other for file control. The son also realized that his manageable switch-statements were getting rather long. While they compiled just fine, he became increasingly uncomfortable looking at a two-page switch-statement and dreaded the day when another programmer would see them.
This unease with the direction of his software was the son’s growing awareness of a code smell. At a high level, the son failed to encapsulate what would be changing. As a result, he’s faced with the task of repeatedly modifying the same files with the addition of every new ingredient. Assuming that he did this in an object-oriented language, he also failed to leverage polymorphism and instead opted for switch-statements.
7.1.2. Step 2: Establish Test Cases.
Resolved to set things right, the son quickly wrote some test scripts that queried the ingredient’s information and compared them to values that he new to be right. When he’d run the test script, a debug window would simply display a “Tests Pass” message assuming everything was correct.
7.1.3. Step 3: Develop a Plan for Design Changes.
With his test suite in place, the son sat and thought about how he would correct his design. The simplest approach he came up with would be to have a class called Ingredient. All the data about an ingredient would now be attributes of the class rather than scattered throughout the software in switch statements. The array that held an enumeration of ingredients in stock became an array of Ingredient objects. The blocks of switch-statements became single line calls to the Ingredient’s attribute’s accessor.
7.1.4. Step 4: Implement the Plan.
With a plan in place, he implemented it in only a few hours.
7.1.5. Step 5: Re-run the Test Cases.
When he was done, he reran his test scripts to verify that the external behavior of the software had not changed.
7.1.6. Benefits Achieved.
The new software design allowed him to encapsulate what would be changing in future updates. Adding a new ingredient was as easy as instantiating a new Ingredient object with its unique details completely contained within the object.
7.2. Case Study 2: Aircraft Subsystem Software
A software application controlling an aircraft subsystem is the subject of our second case study. This software system is written in C++ and is composed of approximately 650 .cpp (implementation) files and 50 .h (header) files. An extensive number of attributes are required to be initialized upon application startup. These attributes are scattered among dozens of classes. The original implementation involved initializing each object’s attributes in the class’ constructor. Many constructors also required references to utility objects. Additionally, the software design is such that only one object of each concrete class is required, yet no safeguards were in place to ensure that additional objects were not inadvertently created. We will discuss how this application was refactored according to our five-step framework.
7.2.1. Step 1: Identify the Need for Refactoring.
The design described above had a few inherent problems that the software design lead desired to address:
  1. Each of the approximately 650 .cpp files has its own unit test, each of which requires the creation of a dummy object of the associated class to use for testing. Any change that affects the signature of the constructor or any of the attributes initialized therein has the potential to cause all unit tests associated with that class to fail. This leads to the potential for excessive rework of multiple unit tests for trivial code changes where only one unit test should have to be updated.
  2. While the original design never instantiated more than one object of each class, there was no safeguard in place to prevent instantiation of multiple objects against the design intent.
  3. The software design lead felt that refactoring the code to address the issues described above would increase its understandability and more clearly convey the design intent. According to Fowler [1], this on its own is a perfectly legitimate reason to refactor.
7.2.2. Step 2: Establish Test Cases.
Per the established software development process, a complete suite of unit tests was already in place for the application.
7.2.3. Step 3: Develop a Plan for Design Changes.
The software design lead analyzed the problems outlined in Step 1 and settled on the following set of refactorings:
1. Instantiate objects through an instantiate() method that calls the default constructor for the class. The visibility of the constructor will be made private to prevent instantiation of objects except through the public instantiate() method.
2. Implement the Singleton design pattern to avoid instantiation of multiple objects of any class. This is enabled by the addition of the instantiate() methods. Within each instantiate() method, a check will be made to determine whether an object has already been created. If not, an object will be created and a pointer to the object returned. If an object does exist, a pointer to the existing object will simply be returned without creating a new object.
3. Move initialization of attributes out of constructors and into an init() method for each class. As a result, each class’ constructor will now be an empty default constructor by design. Updates that would formerly have affected the constructor and potentially all unit tests associated with the class will now affect only the init() method and its single unit test. References to utility objects formerly required as parameters to the constructors will now be implemented as calls to the utility class’ instantiate() methods within the init() method of the object being initialized. In the refactored code, objects will now be instantiated and initialized in a single statement of the form Class::instantiate()->init();.
7.2.4. Step 4: Implement the Plan.
With the plan in place, the software design lead implemented the changes in the code. The required updates were straightforward and did not take a substantial amount of time to implement. In an attempt to minimize the impact to ongoing updates for planned releases, the code changes were initially made in parallel with formal changes targeted to the next planned release. Because the modules affected by the refactorings were not affected by any upcoming formal changes, the refactoring updates could be held out of the formal release baselines until a convenient implementation point could be determined.
7.2.5. Step 5: Re-run the Test Cases.
As mentioned previously, unit test rework is unavoidable after certain refactorings due to the nature of the code changes involved. In this case, due to the fact that the object instantiation technique was changed, each individual unit test will require superficial—although not necessarily trivial—updates before it can be re-run. This makes it impossible to follow the ideal process of running the exact same suite of unit tests without modification before and after refactoring to ensure that no functionality was affected. Fortunately, however, only the “framework” surrounding the overall sequence of test cases for each unit test is affected; the individual test cases themselves are not affected, thus preserving the integrity of the unit test suite as a means of validating that the refactorings have not altered the externally-visible behavior of the code.
Another deviation from the ideal refactoring process is that due to the need to rework all unit tests (approximately 650), it was impractical to re-run the unit test suite after each individual refactoring technique was applied. Instead, all code changes were incorporated at once and the unit tests subsequently updated and run against the complete set of code changes.
7.2.6. Benefits Achieved.
Several benefits are expected to result from the refactoring effort:
1. The code will be more understandable and more clearly convey the intent of the design.
2. The code will be more maintainable as a result.
3. Unit test maintenance will be simplified as changes to attribute initialization will no longer have a ripple effect through multiple unit tests.
4. Implementation of the Singleton pattern will add robustness by insuring against inadvertent creation of multiple objects of a given class.
7.2.7. Drawbacks.
While the benefits are expected to be tangible, several drawbacks call into question whether proceeding with these refactorings was the right decision at the right time.
1. At the time of this writing, the process of updating individual unit tests has been ongoing for over two months, with a significant number of tests still remaining to be updated. Developers have been borrowed from other teams to help with the unit test updates. It is anticipated that all unit test rework will be completed by the certification deadline, but the amount of effort that will have been expended is substantial. It is late in the development cycle of this application with relatively few significant changes expected in the future, so it remains to be seen whether a net time savings will be achieved over the product’s remaining lifetime due to the improved unit test and code maintainability.
2. The time required to rework the entire unit test suite took away significantly from time that could have been used to implement requirements changes desired by the systems engineering team in the release in which the refactorings were incorporated. This delays the deployment of fixes to the aircraft and ultimately to the customer.
3. Due to the relocation of attributes from the constructors to init() methods, integration test cases (separate from unit tests) were also significantly impacted since they reference each variable through a fully-qualified identifier. In an attempt to minimize the impact to the integration test team, the software design lead worked with the test lead to help update the suite of integration tests. However, until the integration test suite is successfully run against the updated code, there remains a risk that additional test case rework could be required to make the test suite compatible.
4. Current policy does not require ALL unit tests to be re-run for each release; only unit tests associated with units that have changed in a given release must be re-run (this policy does not apply to integration tests). As long as this policy is in place, the payoff will be diminished because the “ripple effect” eliminated through refactoring would not have been realized since the additional unit tests that would have been affected are not required to be re-run. The only benefit would be slightly reduced effort to update the unit tests for units that were updated in a given release.

8. Summary

In this paper we introduced the subject of refactoring and presented our viewpoint that periodic refactoring, done wisely, is an effective strategy to keep one’s code understandable and maintainable, allowing future updates to be made more quickly and with less effort, for a net time savings benefit over the product’s lifecycle. We summarized several criticisms of refactoring and offered our rebuttals. We then proposed a high-level, five-step framework for planning and performing refactoring projects. We illustrated the application of our framework through two case studies, the first of which we consider an example of when refactoring was clearly appropriate, and the second of which we deem more questionable given the timing of when the refactoring was performed.

9. References

[1] Martin Fowler, Refactoring: improving the design of existing code, Addison Wesley, Boston, 1999.
[2] A. Van Deursen and L. Moonen, “The Video Store Revisited - Thoughts on Refactoring and Testing”, Proc of the third International Conference on eXtreme Programming and Flexible Processes in Software
Engineering XP 2002, Sardinia, Italy.
[3] Danijel Arsenovski. “Debunking Common Refactoring Misconceptions.” 7/16/2008. InfoQ.com. Viewed 2/27/2010.
[4] F. Khomh, M. Di Penta and Y. Gueheneuc, “An Exploratory Study of the Impact of Code Smells on Software Change-proneness”, Reverse Engineering, 2009. WCRE '09. 16th Working Conference on, IEEE, 2009, pp. 75-84.
[5] Robert X. Cringely. “May the Source Be With You: Maybe One Key to Strengthening Open Source is Just Differentiating Between Work and Play.” I, Cringely. 5/1/2003. PBS.org. Viewed 2/27/2010. .
[6] F. Bourquin and R.K. Keller, “High-impact Refactoring Based on Architecture Violations”, Software Maintenance and Reengineering, 2007. CSMR '07. 11th European Conference on, IEEE, 2007, pp. 149-158.
[7] S. McConnell, Code Complete (2nd edition), Microsoft Press, Redmond, WA, 2004.
[8] J. Viljamaa, “Refactoring I — Basics and Motivation”, Paper, Seminar on Programming Paradigms, University of Helsinki, October 2000.
[9] G. Keefer, “Extreme Programming Considered Harmful for Reliable Software Development”, Proceedings of the 6th Conference on Quality Engineering in Software Technology, Germany, Dec. 2002, pp. 129–141.
[10] JB Rainsberger. “Refactoring: Where do I Start?” StickyMinds.com. Viewed 2/27/2010. .
[11] Tony Hopkinson. “Developers and managers. What does refactoring mean to you?” Tech Republic. 5/31/2009. Viewed 2/27/2010. .

Wednesday, May 12, 2010

Lugaru - Part 1

For the last several months, I've been following the progress of Wolfire Games - an independent game developer who is taking a different approach to game design. They maintain an active blog, detailing their progress with their newest game as well as articles on game design and theory. Yesterday they released their first game Lugaru as an open source project. For those reading this that don't comprehend what that means, (I'm looking at my sister - my only subscriber) it means that the foundational code that was used to create the game has been posted on the web with the intention that it be read, changed, improved, and used!! Normally, that is a closely guarded company secret, but some will release it for various reasons. The motivation for Wolfire releasing the source of Lugaru is a commitment to continue being awesome!!

I spent some time today reviewing their code. It was the first time I had looked at the source for a decently sized application. Opening a few files, I began to pick up on their coding style and design. Over the last year, I've privately studied the concept of refactoring, the process for improving the design of existing code. My hope is that I can use those concepts to improve the design of Lugaru and use this blog to document my thoughts and progress. Here we go...

Saturday, June 13, 2009

SOAP vs. REST

A quick apology to all the family members that are signed up to this blog. I promise to resume personal musings soon!

My class on Service Oriented Computing is discussing two paradigms of web architecture - SOAP (Simple Object Access Protocol) and REST (Representational state transfer).

REST is an architecture is a style that is used heavily throughout the web. Once we spent a lecture learning further about this and looking at examples, I was blown away at how often it's used. It would be like listening to a lecture on bricks and the types of bricks and the intricacy of brick laying and then stepping outside and look at a building. You're blown away at the new perspective you have for something that you took for granted your whole life.

REST is very simple. It's principles, (blatantly lifted from wikipedia) are:
  • Application state and functionality are abstracted into resources
  • Every resource is uniquely addressable using Uniform Resource Identifiers
  • All resources share a uniform interface for the transfer of state between client and resource, consisting of
    • A constrained set of well-defined operations (HTTP GET, POST etc)
    • A constrained set of content types, optionally supporting code on demand
  • A protocol which is:
    • Client-server
    • Stateless
    • Cacheable
    • Layered
Examples of RESTful web services include Flickr and Twitter.

At the time I read about SOAP in Chapter 4 of "XML, Web Services, and the Data Revolution," I thought to myself, "this seems very straight forward." SOAP uses the http transfer protocol with an XML payload. The payload, in turn, has a similar structure of an optional header plus a body - Easy (at least in theory.)

SOAP services require this structure for requests which isn't as easy to do as REST.