Skip to content

Archive

Category: Agile Teams

This is a quick introduction to unit testing with XCode. This article does not provide a conceptual overview to unit testing.

As a first step. I did the following:

  1. Go to the project browser and select [ add > new file ]
  2. Select ‘objective c test case class’
  3. The file that opens immediately seems to contain a couple of tests. That, and a link to an overview document graciously provided by Apple.
  4. So I click, and read everything patiently (well, I’m planning to. I can display a lot of patience at times) (1).

One thing worth mentioning is that, up to stage 4 and notwithstanding footnote (1), this is a perfect introduction to unit testing, and a perfect approach to documentation. The IDE provides the way to get us started with what we want to do. The code hyperlinks the documentation.

Setting up a target for unit testing

Now to the point, what we want is write a test suite and, minimally (assuming we don’t have continuous integration, yet) wire it to the main project, so the test suite runs whenever we build.

  1. We need a target to run the test suite. Go to [ project > New Target > Unit Test Bundle ].
  2. Make a group to hold your test classes (Optional. We might as well dump everything, everywhere).
  3. At this point I deleted my first test class and re-created it. That’s because we need to add the test class to the test target (NOT to the main target, so uncheck that when going through the wizard) and the easy-clicky way is via the wizard, when we create a new test class.
  4. Switch the active target to Test (or whatever your test target is named)  and target the simulator (otherwise tests will skip).
  5. Press build.
  6. Press [Build]. If you haven’t modified the sample test class, this should run OK.
  7. Now modify the test class to fail a test, and press ‘Build’. This should then fail the build. If you check build errors and keep unfolding, you’ll eventually find a reference to your failed test, along with whatever you set the assert to output when the test fails.

OK, this didn’t work for me at first. In the sample test class, reassign USE_APPLICATION_UNIT_TEST to zero. USE_APPLICATION_UNIT_TEST doesn’t seem to be for unit tests; this is for tests that require your application (or some other GUI, not sure… ) to be running while you test.

NOTE: I have somehow gotten into the habit of stripping #import <Foundation/Foundation.h> off my .h files. Well. If you’re doing this, you’ll get errors when trying to compile your project classes to the test target. You need to add the project’s precompiled header (*.pch) to the build configuration for the test target (see below).

Running tests every time you build the main project

Now, this is trivial and elegant at once. To ensure we pass the tests before even thinking about running our app, all we need to do is drag the test target inside the main target.

Next time we build (targeting the simulator) test classes compile and run before actually running our app. If the tests fail, nothing runs until we fix the tests :)

Troubleshooting

There are three practical issues that tend to occur when trying to put legacy code under unit test with XCode. In whatever case this results in stupidly broken builds.

  1. Most of your existing source files are missing from the Test target.
    => expand the main target for your project; expand the test target; now select Compile Sources in your main target. select all files in the Compile Sources group (do it from the top right panel) and drag them into Compile Sources in the test target.
  2. Frameworks in use  are missing from the test target.
    => use the same approach as in (1). This time, duplicate content from Link Binary with Libraries.
  3. The precompiled header (.pch file) isn’t applied to the test target.
    1. Right click on the main target and choose ‘get info’.
    2. Select the build tab.
    3. Under Configuration: (top left) pick All Configurations
    4. Scroll down until you find the ‘Prefix header’ entry in the ‘GCC 2.2 – Language’ section. Copy the pch path/name and duplicate it to your Test target settings.

(1) At this stage, not everything being as perfect as it ought to be, I bothered popping a mail window and sending the link to myself. I wished I had an iPad icon on my desktop and could just drag the safari window to it – or the link. Yea. Kiss the future.

Today I watched Julie and Julia (or vice-versa) and I couldn’t help but get sympathetic to a feel-good movie about the ups, downs and hazards of a wacky personal project. That appropriately reminded me too, that I seemingly never set a hard deadline on my game project. As Brookes quoted:

Good cooking takes time. If you are made to wait, it is to serve you better, and to please you. [The Mythical Man Month p. 13 - originally: Menu of Restaurant Antoine, New Orleans.]

I won’t get into explaining how you cook-up a 12 hours pork stew, or why giving estimates for product delivery is pure technical arrogance facing money-wise operational goals. I’ve been dragging along (not always speed-jetting) carrying a mix of anxiety and anticipation, while clinging to ‘deliver often, deliver early’ as a mantra better than worth repeating every day.

‘Deliver often, deliver early’ could only mean either of two things in my case:

  • Make a small product ‘just for the sake of it’.
    I clearly missed several opportunities in this area; in part because I’d done two small prototypes (A flash-card thing for chinese and a nicely animated icon matching game) and couldn’t see how this could either make money or, more importantly, drive the development of something more ambitious); in part because instead of starting making/learning/porting a 3D engine (a concrete, pragmatic move towards making a game), I wrote an (optional) behavior scripting system that merely implied the many components needed in a finished product.
  • Define a ‘minimally marketable’ design for my product, and stick to it.
    On this side I’ve been more constant. While there’s a couple of non essential features that won’t make their way into the first release, I clearly anticipated early on what I needed to make my game usable, and keep it ‘minimally fun’.

Unsurprisingly, I didn’t start getting any good feedback from my casual play testers until I managed to tick all the boxes off ‘minimally playable and fun’. And now I do. I hear that my game is… …playable and fun.

OK, I lied. What it feels more like is, I’ve been working on this huge pile of cool stuff, until the day I patched it all together, only to realize it was neither playable, nor fun. I could have released a fun, playable game maybe 3 months ago. What’s true, however, is that that game wouldn’t have been as good as my prototype is now, because most of the cool stuff is making its way into the final product.

For the record, here’s a few things that aren’t needed to make a fun, playable 3D game, but still nice to have. In other words, the things that you might leave out if you need to release and make money ASAP:

  • Support for exporting animations (you could make a space shooter instead, or whatever…).
  • Procedural grass.
  • Lighting (ever played Katamari on your iTouch?)
  • Behavior scripting
  • Cool camera management
  • Modeling (or procedurally generating) natural landscapes

‘By the way, maybe you should check what seems like an interesting article temptatively rooting Agile into Brookes’ book.

One more thing. Coding is nothing like cooking. Cooking may take 6 hours. Cooking may take a day. But cooking’s just cooking. Coding up stuff takes ages.

Plus, cooking is a healthy activity where you get to stand, scurry and scour the local markets for fresh veg, meat and fish. My back is half-killing me.

…and we’re back!

Here’s two contradictory propositions about test first development (TFD):

  1. TFD helps you to design your software better.
  2. TFD should not change the way you design your software.

At the moment I’m still designing a library for intermediate level developers. Static members, for all the bad they are, often make my programming life simpler without jeopardizing the future of my code altogether, so I guess among intermediate developers some will feel my pain better than I do, and develop a negative understanding of the following, short sighted truths if I do not provide static methods as part of the kit:

  • Factory methods are a good alternative to constructors when constructors are not available (minor)
  • Where one, and one only, instance of a service is, for the foreseeable future, desirable, static methods avoid burdening oneself, or other developers, with the need to bounce a singleton around a software system (major).

As a quick reminder or introduction, here’s why TFD (among several other, theoretical and formal, protagonists) doesn’t like static members:

  1. A unit test is validating a class. This is done to ensure that the object will behave as expected, so that, if this object appears to misbehave in some way, you can rely on the unit test failing or passing to tell you either yes, the object is misbehaving, or no, the object is mis-informed – interacting with something else, and that thing must be broken.
  2. A static member is a member that you cannot, in many languages, replace by a stub. That means that you can’t isolate the behavior of A, using static member S, from the behavior of the system {A,S}.
  3. Since we can’t easily tell whether A or S or {A,S} is failing when A appears to fail, the unit test for A is imperfect. More importantly, {A,S} is harder to maintain.
  4. Ergo, static members are undesirable.

As an API designer, what should I do? Well maybe I’ll change my mind later, but for now, here is how I feel about it:

  1. If I were designing an API for experienced developers, I would most likely avoid statics. I couldn’t use them anyway because the use of static members would taint my API. Not all experienced developers have a deep grasp of why statics might be bad. This is not how humans learn. Most experienced developers feel that static members are bad. This is what developers learn.
  2. Since I am designing an API for intermediate developers, I may be ill inspired not to provide them with convenient static functions. In fact I should wonder whether my static functions are a poor substitute for globals.
  3. I need software written using my libraries to be unit-testable.
  4. Ergo, I shall have to write a stub version of my libraries – an empty shell containing all classes and function signatures in my libraries. This way developers can unit test software written using my library, and enjoy readily available stubs providing support for testing (e.g., logging API calls)

I find the ‘case of untestable static members’ delectable. I may be more against static members that many developers are, but today I’ve been the devil’s advocate, so I’m just about to turn my jacket but…

…not in this post.

Once again I’m about to do the wrong thing – forgo my assumption that stumbling steps are enlightening and not writing this post. I’ve just completed writing a unit test for a foo():Void haXe function with no flow control to its name. More about this another time.

Bear in mind, this article isn’t about what is or isn’t worth testing. My first step into TFD consists in having a fair go at writing tests before I write the code, without breaking my usual design flow – correcting my design flow is OK, if that’s what it amounts to. Correcting, yes – breaking, no.

Can we test the main() method? Do we want to?

I’m taking over a colleague’s work, now I’m sniffing the Main class and thinking hell, what shall I do with it. Test? Well yea, I should test it first. Since there are not unit tests for this project yet, I might as well consider it to be legacy code.

My colleague did something that I tend not to do – they just use Main.main() to instantiate a singleton. I guess it should really be a singleton but the point is, they escaped the static logic behind main. So I’ll follow in their steps and instead of testing main(), I should probably test whatever gets instantiated by main.

Unfortunately, as a good initializer does, mine mostly invokes other initializers. Essentially a constructor is a static function. So yea I could pass an array of  classes and instantiate them by reflection, but I don’t want to do that. Too much trouble.

I had the neat idea to just call the constructor to make sure it works, but that’s not a unit test – that’s actually testing the whole f*** thing. Too integrated.

Why I don’t feel like testing main() and constructors anyway?

I’d like to test how my code changes the software system I’m building. Although I’ll come back to this later and discuss, a function that doesn’t return value isn’t making local changes, so from this point of view there’s nothing to test inside the unit.

So what shall I do?

Flowing with TFD?

Here’s my problem with test first development right now. I need a method that flows. If it flows slowly, fine, but right now my flow is frozen. Let’s get flowing and try this…

  • I’ll always write a test for each function. That’s easier than  asking everytime whether to write a test.
  • If I can’t test the function, at least, I’ll document the test function with my design intention for the non-test function – that’s because I need somewhere to write my design intention. Then I’ll past this doc in the function’s code body so I can use it to write the actual code (that’s because my STM is very limited)

I’d like to still call these functions to make sure they don’t fail. That wouldn’t be a unit test, however. Oh my…

Working notes

No time for a conclusion today – just a few ideas

  • In absolute, I’d really like to write my tests first. This is why I’d prefer to write an ‘untestedConstructor’ function rather than no function at all in the test class. untestedConstructor() may seem horribly pedant, but I like it. This way you know I haven’t tested this by looking at the tests. Also, I can move design comments to the test class – if the tests are gonna stand for documentation, I think that’s alright.
  • So far, I only note I find it difficult to test root initializers. The type dependencies involved make it appear so difficult as to be counter productive. However, even with a statically typed language such as C++, doing this is likely not impossible (just map another .c file to the same .h file?).
  • One view about testing is that only state transitions matter. ‘unit’ testing consists in testing a class. Applying this, we could say that a method that causes no local change needn’t be tested.
  • Surely, a controller doesn’t manipulate it’s own state. A controller should hold no state. Does it mean a controller shouldn’t be tested? Oh my, controllers are were the case logic of a program resides… Maybe a productive idea – how about if we extract the case logic, so that the case decision can be returned from a function as a value. My counter-intuitive bet is that such functions should then be private – unfortunately, I heard over and over that a good unit test focuses on the public interface of a class. More contradiction. Or maybe we should just distinguish ‘do’ classes from ‘case’ classes; then we’ll have a good reason to make ‘case logic’ methods public.
  • Another view about testing might be that it is the behaviour of a class we are trying to test. Even if a class doesn’t manipulate its own state, we still want to assert whether the changes it makes to the outside world are as intended. TDD programmers that use mock objects liberally seem to be on this side.

Enough musings for today.

In a project involving the creation of a new software library, the architect’s role is somehow similar to that of a product manager. The product is the library, and the architect may be in charge of maintaining the vision associated with this product. Reflecting on personal experience, this article exposes what an architect may expect while collaborating within a moderately sized team. I have found that, should one or more of these elements be missing, waste may occur, and product integrity may be compromised. Here’s the list.

1. Trust and Respect

The architect is in charge of the product’s vision – the API or library is the product. Other team members should trust and respect the architect’s work, assuming the following to be true:

  • (a) The architect has sufficient expertise in the target domain to formulate reasonable propositions.
  • (b) The architect has much more time than the rest of the team to consider, and direct the team towards, short to long term API development strategies.
  • (c) The architect is defining functionality that will be at least attractive, and usually valid, from the API user’s point of view(1).

If a team member feels that (a) or (c) can be disproven, then they should raise the matter with their team lead or technical director.

If a team member finds that (b) is untrue, they should try to support the architect better, so they find time to do their job (see support)

2. Understanding good design

Good design doesn’t require locally perfect APIs, nor even striving towards a hypothetical, holistically perfect API.

Good design means creating an API with the following qualities:

  • Fitness for purpose - If the API can be successfully used to do the job it is intended to, local flaws matter little.
  • Consistency - It is often better to strive for consistency than to strive for local perfection.
  • Timely delivery - While this may not always be critical, there are often operational goals associated with the development of an API.

Maybe the most essential point about good design is that there are always several, roughly equivalent ways to design a fit for purpose API. Further, few APIs partaking a fit for purpose library, if any, will be ‘locally perfect’.

Consistency requires giving up a little perfection here and there. Timely delivery occasionally requires taking shortcuts, deferring a re-factoring, or even endorsing what will ultimately prove to be a design of significantly lesser quality. Neither needs be a concern as long as fitness for purpose is not affected.

3. Support

If team members understand good design and trust the architect – or at least, respect their expertise – they are ready to support API development. What does this mean?

To support API development means to participate in a team effort promoting good design, versus promoting personal twists and quirks. Here are a few things that a good team member will do:

  • Review the design either with trust and respect, or with a degree of domain expertise matching that of the architect. If a team member isn’t interested in the overall design or doesn’t understand the product vision, they do not qualify as a peer (2), and therefore should defer to the architect’s design decisions.
  • Situate implementation related concerns within a wider scope. A team member should discuss implementation related problems only insofar as such problems may invalidate the proposed API, e.g. by making realizing the desired functionality impossible (logical flaw) or intractable (lack of time), or by significantly limiting the performance of the resulting API (or that of a product dependent on such API).
    A team member should not expose or discuss all implementation details to/with their architect. The architect won’t be able to do a good job if they spend their time learning and analyzing such or such detail versus designing the API and maintaining the development strategy.
  • Respect the API coding style and semantics. Stylistic matters… …don’t matter. Consistency in naming APIs, whether using explicit or implicit policies, is, however, essential. Discuss stylistic matters at the beginning of the project if you can’t help it. Learn policies, either by reading them or looking at the specs, if you’re joining an existing project.
  • Propose improvements, not cancellations – APIS are means to ends. Suggesting that an API isn’t useful may be OK if a team member genuinely understands the product vision and the library’s design goals. Otherwise, the architect should legitimately feel disappointed that they get no support from their team member.
  • Attempt not to overload APIs – an API is built atop other APIs. The target product isn’t the sum of all its components. It is intended as a domain specific, enabling technology. If the API omits to set such or such parameter, it is quite possible that the implementation is expected to take care of this on a best effort basis – just forwarding additional, unrequited parameters risks exposing details better hidden from the API user.
  • Know when is the time to discuss APIs – Whenever work pressure allows it, the architect will discuss both details and general orientations with other staff, within and without the team. The architect may be trying to assert feasibility, be concerned about timely delivery or just looking for advice; conversely, team members may feel that they have concerns about the API at any thinkable level. So when is the time to discuss APIs?
    • Discuss once and only once. Moving forward requires ‘moving in the same direction for a while’ . Discussing things over and over reduces the chances that the library will be delivered on time.
    • Re-discuss when and as new information is being uncovered. Agile API design is incremental. New information is likely to be discovered  during this process. If a team member encounters new information that significantly questions previous choices, they must expose relevant evidence.
      Ways to discover new information may include writing proofs of concept, deep thinking, testing APIs with code that uses them and other methods.
  • Endorse and implement design decisions. Ultimately, individual team members may or may not feel happy about design decisions. So if a team member perceives a decision to be flawed against the architect’s or the rest of the team’s opinion, what should they do (see below for more options)? They should go ahead, endorse and implement either the requested design, or at least a proof of concept. libraries are code. Prove or disprove a design using running code rather than re-discussing issues over and over. Everybody makes mistakes; everybody is sometimes right against everybody else. The burden of proof is on running code.
  • Commonsense. Unless an architect is extremely methodic and has a lot of time on their hands, they are likely to miss out details while drafting APIs. The API designer’s goal is to communicate enough about the APIs that they can be implemented by a supportive team endowed with commonsense. The architect will expect an implementer to fill in the details, not stare at the holes.
  • Awareness. A team member should strive to keep pace with the developing API, not just look at things from their own, narrow end. Awareness allows a team member to support design by refining their understanding of the emerging product. Without awareness, a team member will neither acquire, nor retain peer status, therefore may no longer contribute to design discussions, and may misunderstand the intent behind APIs pushed to them.
    Even if a team member does not wish to involve in the design process, they should keep a reasonable degree of awareness. In this case, consider awareness a natural extension of commonsense.

4. Collaboration

Within an API development team, collaboration means at least two separate things:

  • Help each other work faster by learning each other’s code and promoting code reuse.
    Share code; share advice; share knowledge. When an API becomes large enough, opportunities for sharing and reusing code naturally multiply. While the architect may anticipate such opportunities, modeling dependencies internal to an API implementation is not the primary role of the API designer. Team players quickly detect opportunities for reuse, for example by reviewing each other’s code.
  • Non-competition. The team must strive to build the library together. There are various ways in which team members can drift away from this goal.
    Team members may develop frustration over time because the wider goals of the API and library development process stifle their individual creativity.
    Such or such team member may favor a utility library over the team’s choice…
    Team members may develop an alternative vision while developing awareness…
    Non competition means that, before asking oneself ‘Can I do this better than what the spec/implementation design says?‘, a team member should ask themselves: ‘Will my choice promote our success‘. A team member may feel that deviating from the team’s choices and re-writing existing libraries signals excellence. Unfortunately, the better is usually the enemy of the good in this case. Even moderately large libraries require canvassing and compromises.

5. Knowing how to say NO

Finally, what should a team member do when they disagree with the team and/or the architect?

  1. Disagree, once and only once – Team members should explain why they disagree. This may often help shift or correct the team’s decision. Once and only once also means that, once a general direction has been agreed, it is not OK to re-discuss this orientation while dealing with every instance of the general case.
  2. Apply deference and trust. This is a simple rule that may be more difficult to apply from a team lead’s point of view. If the team lead and the architect are not the same person, then the team lead should normally defer to the architect, unless they combine peer status with authority.
    Whenever the team lead overrides or denigrates the architect’s decisions (beyond seeking consensus), they are practically undermining the architect’s role. This can be OK from time to time if the team lead is actually right. Otherwise – or simply if it’s impossible to figure what’s right or wrong – this will likely lead to the architect being forced to step down. Why? Not only because this makes it hard for the architect to do their job, but also because, from the architect’s point of view, continued disagreements with the team lead can lead to managerial action against the architect.
  3. Get help from above - Get help from somebody who cumulates peer status and, roughly speaking, experience above or at the same level as, the architect’s. Getting help from above is only meaningful if a team member feels that they are addressing a critical / major issue that will compromise ‘good design’ as described in (2). Not everybody senior and experienced considers clearing misunderstandings and nit-picking as a valuable use of their time.
    The architect is not mandated to validate all of their decisions with a senior, more experienced staff. First of all there might not be such a person around. Should there be, the architect won’t be doing their job if they have to routinely rely on external advice. Regularly asking your architect if they have discussed this and that with such and such senior staff evaluates to questioning their competence.
  4. Avoid sarcasm, irony and bad omens. Agreeing to something while promising it will lead to disaster or waste is not just promoting bad mood in the team, it is also irresponsible. If a team member has serious issues with the team or the architect’s decisions, they should appeal to a senior (e.g., technical director) and, ultimately, defer to their advice.

Right – I don’t think this stuff is too original, and I suspect a lot of it applies to team work in general, not just API design or the architect’s role. If you’re having trouble understanding what’s going on in your team and suspect there are little grains of sand between the cogs and wheels, I hope this helps.

(1) Depending on the library’s target audience, different constraints may define what is an attractive or valid API. Writing an API for a casual PHP developer does not generate the same constraints as writing an API for financial banking. What is essential to financial banking programmers may be unthinkable for casual script writers.

(2) Several points in this article depend on understanding so called ‘peer status’. Peer status (in short, having expertise relevant to the design problems underlying API design) is not a global property of a team member. Peer status is more like a variable – Whether a team member is competent to address a design problem should often be considered on a case basis.

How far can we go into the writing of an application without losing momentum?

As an application becomes richer, it typically becomes more complex. There are two aspects to this:

  • Logical complexity increases as a result of new functionality binding to existing functionality.
  • Physical complexity increases as a result of increased application size.

The result of increasing complexity is that developers find it increasingly challenging to add new features to an application. This complexity manifests itself in two ways:

  1. Your workspace is increasingly cluttered with irrelevant entities – entities that do not contribute to the feature you are developing
  2. Your code is increasingly dependant on existing elements of functionality.

Very large applications are typically broken down into modules. Modules reduce logical complexity by providing narrowing interfaces; physical complexity is reduced by keeping all the code for a given module separate from the code for other modules.

Feature architectures enforce micro-modularity:

  • Each feature typically maps a single value increment or story.
  • Features are abstracted from each other using channeled notifications.

Why design feature architectures?

Feature architectures are derived from story driven development. In agile, story driven development, developers collaborate towards the development of a given story cutting across the layers of a selected framework. Agile ensures that developers deliver value faster; in suitable contexts (assuming that value can be delivered incrementally), this maximises profitability while reducing the risk to produce large amounts of unused functionality.

Feature architectures take advantage of the separation between source spaces and runtime spaces to allow developing well formed (layered) runtime architectures while limiting linearly growing physical and logical complexity. This allows developers to focus on delivering atomic value increments without incurring mounting familiarization costs and splitting development across framework layers. Also, this may allow fairly large developer teams to collaborate without facing large communication costs.

In a nutshell, feature architectures and micro-modularity target value driven separation. In contrast, existing separation strategies and meta-patterns provide abstract roles that can be re-instantiated in a variety of contexts (the MVC meta-pattern is an example). This affords industry endorsement for high level design solutions. Beyond, classic separation models emphasize a component drive approach. While this means that component cores can be developed in parallel, this may result in glossing over communication interfaces between components.

Layered, component oriented architectures emphasize potential and structural integrity and are more suitable for long term investments and library development. Feature architectures emphasize value increments and may be ideal for writing application front ends:

  • Reusable data and view components are already available
  • Applications are feature rich, yet do not involve much complex code.
  • Value increments typically involve cutting across layered architectures.

Working using a feature architecture

Feature architectures require developers to package features separately. Each feature is developed as an integrated micro-module – if you are writing an application, a feature will probably bundle view, model and controller functionality.

  1. Each developer or pair are responsible for designing and implementing a feature derived from a user story.
  2. The core functionality involved in developing a feature is developed independently from other features.
  3. The source code related to a given feature is bundled within a separate package.
  4. Developers integrate their feature by implementing handlers for channeled notifications.
  5. Developers expose required resources by providing external notifications.
  6. All source code for a given feature may be removed without causing the application to break at compile time.

Mmh… this may not be the best introduction to feature architectures I could write. I’ll try to improve on that and will also provide an application template very soon.

In agile development, the emphasis is on delivering business value incrementally. While this explicits features within software cycle management, it may also reduce the developers’ visibility over software architecture; this may part in explain why agile methods rely heavily on refactoring.

In contrast, classic development approaches emphasize planning and upfront design. While this may promote architectural integrity, it has been largely demonstrated that  this reduces both customer satisfaction and software profitability, typically resulting in a large amount of unused functionality.
When debugging and processing change requests, development teams naturally adopt a feature oriented approach; this is because bugs and change requests are normally raised by end users.

Overall, software features, whether implicit or explicit to the development cycle as agile stories or bugs, remain largely implicit to code-bases because the high level structure of software applications follows a technical orientation – a paramount example in front end development being the unavoidable Model View Controller meta-pattern. Features typically cut across layered architectures. For agile programmers and maintenance teams, completing stories, fixing bugs and resolving change requests requires juggling between application layers. For traditional teams, reduced visibility over application features will result in a higher number of bugs and decreased customer satisfaction.

I define Feature Programming as a set of methods designed to reduce the cost of software development by revealing features within source code. This is mainly achieved in two ways:

  • Feature Annotations using domain semantics allow software architects to retain a layered architecture while increasing feature visibility.
  • Feature Architectures emphasize feature separation over layering when deciding the high level structure of an application.

Both approaches are expected to reduce costs in front end development. While feature annotations provides non invasive, lightweight techniques for teams that want to reduce liabilities in the software cycle, Feature architectures may be very suitable for decentralized and open source application projects and further afford the creation of reusable value increments.

In this article, I cover feature annotations; later I will cover feature architectures in another article and will also demonstrate a small framework guiding the development of feature centric front ends while affording elements of layering using a suitable interpretation of the MVC meta-pattern – stay posted!

Feature Annotations

Annotating features simply requires a fairly disciplined approach to tracing code. Feature annotations can be applied either during development or retrospectively, as a scaffolding technique used to reduce the cost of maintaining legacy code.

Feature annotations requires the following:

  1. One or several feature classes define static functions expressing application features. Such feature traces must be easy to spot and suitable for automated discovery (in Java or ActionScript, you may use $ as a prefix).
  2. Each function in a feature class encapsulates a trace statement. Traces may be logged, output to a console or even displayed in an application specific debug window.
  3. Semantics for feature functions are typically provided either as story names or itemized requirements.
  4. All code that relates to a specific feature must be annotated using trace function invocations (‘trace annotations’). Within an agile process, a developer writing a story will tag their code, and otherwise contributing code, using trace annotations (for each class involved in the developed story, at least one function or constructor should be annotated).
  5. It is important that feature traces can be filtered easily either at runtime or at compile time (commenting out unwanted traces may be sufficient in simple cases).

Feature annotations constitute a non invasive technique. Adding feature annotations cannot break existing code and doesn’t require re-thinking your approach to software architecture. Developers working on agile stories need not be concerned with selecting trace annotations as the same annotation semantics are used over and over during story development.

In spite of this simplicity, feature modeling provides several benefits that will help in drastically increasing a development team’s reactivity to change requests while increasing software maintainability:

  1. If you are doing code reviews, feature annotations reveal and delimit the code related to a given feature and afford validating the code from the point of effectiveness. Classical code reviews validate structural integrity, glossing over functionality scattered across application layers.
  2. New starters observe feature traces at runtime and isolate features in the source; this complements a developer’s understanding of packaging and structure; developers also appreciate the software’s actual uses by reviewing feature classes. This results in decreased learning curves and reinforced consistency across the code-base given multiplied reuse opportunities.
  3. Maintenance teams benefit orderly, channeled traces mapping business logic using domain language. Observing channeled traces and isolating defective features can save hours spent learning control flow and discovering all scattered elements potentially involved in a defect.
  4. Liabilities associated with reuse are reduced given that concurrent, co-located traces provide tell-tale signs of reuse. A developer targeting a given code fragment will be able to determine which elements of functionality are affected, thereby correctly assessing the risk involved in modifying shared resources and identifying functionality that may require regression testing.