Archive for August, 2009

Why does software rot?

Monday, August 31st, 2009

In our first Q.E.D. Hour, we covered the first section of Robert C. Martin's Design Principles and Design Patterns. This first section talks about the symptoms of rotting design.

When software is created, it is a pure rendition of the designer's intent. But over time, as the software changes, it begins to rot. The changes come from users of the system, but the rot comes from within. Robert C. Martin has identified these four symptoms of rotting design:

  • Rigidity - The tendency for changes to ripple through the system, resulting in a resistance to change.
  • Fragility - The tendency for seemingly unrelated components to break when you make a change.
  • Immobility - The difficulty of isolating a component for reuse in a different system.
  • Viscosity - The friction involved in making a change.

Unlike physical goods, software doesn't rot while it's sitting idle. Software rot occurs only as the system changes. These symptoms only manifest when we try to change the software. So we can eliminate rot in one of two ways: prevent the software from changing, or control its root cause.

DependenciesManage your dependencies
The root cause of rot is dependencies. For example, we use a service oriented architecture at work. Many of our services depend upon our Catalog service. That makes Catalog rigid, because we can't change it without affecting the others. Our Shopping service depends upon many other services, which makes it fragile. Changing anything in the system runs the risk of breaking Shopping. This also makes Shopping immobile, because we cannot reuse it without all of its dependencies.

Robert Martin's paper goes on to describe how to invert dependencies in order to manage them. But in our Q.E.D. discussion, we left that for another day. First, we need to learn how to measure our dependencies. Proof is only possible based on the things you can see.

Right now, we are just interested in static dependencies. These are the dependencies between different pieces of code. Later, we'll talk about dynamic dependencies. Dynamic dependencies occur between different pieces of data. Static dependencies occur at compile-time, while dynamic dependencies occur at run-time.

There are six static dependencies visible from the public interface of a class. These are the types of:

  • The base class
  • Implemented interfaces
  • Constructor parameters
  • Method parameters
  • Method returns
  • Properties

If your class accepts returns or inherits a type, then it depends upon that type. These dependencies are visible dependencies, since consumers of your class can see them.

There are several dependencies hidden from the public interface of a class. Four most common are:

  • Internally created objects
  • Static methods of other classes
  • Extension methods
  • Singletons

If your class takes on any of these dependencies, consumers of your class cannot tell. You run the risk of making your code rigid, fragile, and immobile by having hidden dependencies.

Here's my solution
Make dependencies more visible to reduce rot. Turn hidden dependencies into visible dependencies.

Turn internal object creation into a factory. If you need to create a new object, don't just new one up. Delegate that function to a factory. No "new"s is good news.

Turn static method calls into strategies. Calling static methods directly leads to procedural dependency. You are telling the compiler not just what to do, but also exactly which implementation should be used to do it. Instead, use a strategy. Then you can inject the right implementation for each situation without breaking the caller.

Extension methods are a special case of static methods. It is extremely difficult to track dependencies on extension methods, especially since the name of the class declaring the extension method appears nowhere within the code. You have to study the "using" declarations to find them. Extension methods are best used to add behavior to an interface without requiring implementers to add code. Extension methods are worst used as stupid compiler tricks. And don't even get me started on extension methods that check for null.

Probably the worst hidden dependency is the singleton. The problem isn't that there is only one instance. The problem is that anybody can depend upon it. If your class uses a singleton, there is no way of seeing this dependency from the outside. Singletons tend to be very rigid, since so many other classes depend upon them. Avoid the singleton pattern. Instead, rely on an IoC container to inject a single implementation of an interface into all components that require it. This turns a hidden dependency into a visible one (a constructor parameter).

Making all of your dependencies visible is the first step to managing them. Only then can you hope to fight software rot.

Confidence through proof

Sunday, August 30th, 2009

This week marked the inauguration of Q.E.D. Hour. This is a weekly lunch hour where we talk about techniques for writing provably correct software. The goal of Q.E.D. Hour is to gain confidence in our code.

ConfidenceThere are several ways to build confidence in your code. You can write specifications to record what the user wants or how you intend to build it. You can single-step through it in the debugger. Or you can write unit tests.

The problem with all of these confidence builders is that they can only give you confidence in the situations that you've thought about. They can't tell you anything about the situations that you haven't considered.

What if the user does something not in the use case or user story? Do you write another use case to cover that scenario? When does it stop? Can you ever write enough use cases?

You can see what the code is doing when you single-step through it in the debugger, but what would it do if you passed in different parameters? Can you every do enough debugging?

You can see that every unit test you've written passes. What about the unit tests you haven't written? How do you know that the code is correct for all situations? Can you ever write enough tests?

Even 100% code coverage doesn't save you. The line that references "Customer.Name" is covered by tests, but what happens when Customer is null?

Mathematical proof is the only way to know that code is correct under all possible conditions. All of the other techniques are empirical. They only verify the code in one situation. Proof, on the other hand, is deductive. It verifies code for an entire class of situations.

Both empirical and deductive confidence builders are required. Without empirical evidence, you can't be sure that you wrote the code correctly. But without deductive proof, you can't be sure that you wrote the correct code.

Linq and regular expressions: a perfect match

Friday, August 21st, 2009

Both Linq and regular expressions are great ways to write declarative code. When you combine the two, the result is magic.

Here's a utility class that returns a human readable name from a camel-case identifier.

public static class NameUtilities
{
    private static Regex WORD = new Regex(
        // Lower-case letters at the beginning of the word.
        "(^[a-z]+)|" +
        // At least two upper-case letters not followed by a lower case letter.
        "([A-Z]{2,}(?![a-z]))|" +
        // An upper-case letter followed by lower-case letters.
        "([A-Z][a-z]+)");

    public static string HumanReadableName(string identifier)
    {
        // Split the identifier at capitals followed by lower case.
        return WORD.Matches(identifier).OfType<Match>()
            .Select(m => InitialCaps(m.Value))
            .Aggregate((name, part) => name + " " + part);
    }

    private static string InitialCaps(string word)
    {
        if (word.Length < 2)
            return word.ToUpper();
        else
            return word.Substring(0, 1).ToUpper() + word.Substring(1);
    }
}

The Matches method returns a MatchCollection. Since this class predates .NET generics, it implements the untyped IEnumerable. OfType<Match>() is required to safely cast each member to a Match.

Each of the Matches is converted into a capitalized word, and the words are concatenated with intervening spaces. The Aggregate() trick is thanks to Deborah Kurata. Sure, it might be less efficient than StringBuilder.Append(), but measure before you assume.

The combination of two great declarative programming techniques creates a concise, readable piece of code. The same algorithm written imperatively would be much more complex.

The constructor: the strongest of all methods

Thursday, August 13th, 2009

The constructor has a very powerful contract, and one that the compiler proves. The constructor is called once and only once.

We can use this promise to prove some very useful things. We can prove that required properties are set. We can prove that A happens before B (as we can with other prerequisite techniques). But more strongly, we can prove that A does not happen after B.

Required properties
A constructor has to be called. There is no other way to get an instance of an object. If there are any required properties, they should be constructor parameters. Otherwise, there is no way to prove that they've been set.

class ReportRequest
{
    // Required parameters.
    private User _requestedBy;
    private Company _requestedFor;

    // Optional parameters.
    private DateTime? _fromDate;
    private DateTime? _toDate;

    public ReportRequest(User requestedBy, Company requestedFor)
    {
        _requestedBy = requestedBy;
        _requestedFor = requestedFor;
    }

    public User RequestedBy
    {
        get { return _requestedBy; }
    }

    public Company RequestedFor
    {
        get { return _requestedFor; }
    }

    public DateTime? FromDate
    {
        get { return _fromDate; }
        set { _fromDate = value; }
    }

    public DateTime? ToDate
    {
        get { return _toDate; }
        set { _toDate = value; }
    }
}

We can prove that the user requesting the report and the company for which the report is requested are specified. The filter parameters are optional.

Immutable properties
Once a constructor is called, it cannot be called again. This is an extremely powerful contract, and can be used to prove that properties can't change. In the above example, the RequestedBy and RequestedFor properties are immutable. They can only be set by the constructor, which can only be called once.

Immutability is an example of the statement A does not happen after B. A) the property changes. B) the property is set. The property does not change after it is set.

There are other statements of this form that the constructor can prove. For example, a connection string cannot change after a database connection has been established. Here's a snippet of the ADO.NET SqlConnection class:

public class SqlConnection
{
    public SqlConnection();
    public SqlConnection(string connectionString);
    public string ConnectionString { get; set; }
}

Do you see the problem? You cannot prove that the connection string does not change. This class has guard code that throws an exception if you do so. It would be a simple change to make this contract provable:

public class SqlConnection
{
    public SqlConnection(string connectionString);
    public string ConnectionString { get; }
}

Don't waste the constructor
Using a constructor to prove a contract is a powerful capability. It is weakened when the constructor is used for other things.

public class User
{
    private string _userId;
    private string _firstName;
    private string _lastName;

    public User(string userId) :
        this(userId, string.Empty, string.Empty)
    {
    }

    public User(string userId, string firstName, string lastName)
    {
        _userId = userId;
        _firstName = firstName;
        _lastName = lastName;
    }

    public string UserId
    {
        get { return _userId; }
    }

    public string FirstName
    {
        get { return _firstName; }
        set { _firstName = value; }
    }

    public string LastName
    {
        get { return _lastName; }
        set { _lastName = value; }
    }
}

This class has a weak constructor. The name of a user can change, but the ID cannot. An overloaded constructor initializing the name is provided for convenience. But this convenience comes at a price. It is more difficult to see that the user ID is required and immutable. It's still provable, but that information is not called out.

Don't overload the constructor. Don't use it for convenience. Don't initialize mutable properties. When a constructor is used only to set required and immutable properties, the intent is clear, and the proof is easy.

Proving prerequisites

Tuesday, August 11th, 2009

Every piece of code is a theorem. It is a sequence of logical conclusions, each based on the one before, leading up to desired behavior. To validate that behavior, you need to prove the theorem. Even though most compilers don't prove those theorems for you, they can still provide some assistance.

We often have to call methods in a certain order. One method is a prerequisite, the other its successor. For example, we need to validate a shopping cart before we check out. Typically, this is hard to prove.

public class ShoppingCart
{
    /// <summary>
    /// Validate the shopping cart. Throws
    /// ShoppingCartException if there is a problem.
    /// </summary>
    public void Validate() { }

    /// <summary>
    /// Checkout and provide payment for a shopping
    /// cart. Requires that Validate() is called first.
    /// </summary>
    /// <param name="paymentMethod">Method used to pay
    /// for the items in the car.</param>
    public void Checkout(IPaymentMethod paymentMethod) { }
}

The contract is clear, but how do we prove that the caller is following the rules? One way is to keep an internal flag. Set it in Validate(), and check it in Checkout(). If the flag is false, throw an exception.

While this technique works, it is not ideal. It is really no different from a guard clause. It's just a different form of defensive programming. Besides, exceptions are for problems that occur even in well-written software. Calling Checkout() without calling Validate() is a defect, and should never happen at all.

Return from a prerequisite
If we change the interface, we can prove the contract at compile time. It won't need to be verified at run time. One way to do this is to put the successor method into the return of its prerequisite.

public interface IValidatedShoppingCart
{
    /// <summary>
    /// Checkout and provide payment for a shopping
    /// cart.
    /// </summary>
    /// <param name="paymentMethod">Method used to pay
    /// for the items in the car.</param>
    void Checkout(IPaymentMethod paymentMethod);
}

public class ShoppingCart
{
    /// <summary>
    /// Validate the shopping cart. Throws
    /// ShoppingCartException if there is a problem.
    /// </summary>
    public IValidatedShoppingCart Validate() { }
}

The compile-time contract is strong enough that it could be removed from the comment. It's obvious to the caller that they have to call Validate() first.

Pass a parameter to the successor
Another way to prove the contract as compile time is to pass the prerequisite as a parameter to the successor. We could, for example, prove that the caller needs to get a credit card approved before using it to make a purchase. Here's the hard way.

public class CreditCard : IPaymentMethod
{
    /// <summary>
    /// Create a credit card for a customer.
    /// </summary>
    /// <param name="customerInfo">Information
    /// about the card holder.</param>
    public CreditCard(CustomerInfo customerInfo) { }

    /// <summary>
    /// Request approval for a credit card. If
    /// approved, return the payment. If not,
    /// CreditCardException is thrown.
    /// </summary>
    public void Approve() { }
}

We could set a flag when Approve() is called, and check it when the IPaymentMethod is used. But again, it is defensive. There is a better way.

public class CustomerInfo
{
    /// <summary>
    /// Request approval for a credit card. If
    /// approved, return the payment. If not,
    /// CreditCardException is thrown.
    /// </summary>
    /// <returns>Payment that can be used to
    /// purchase items.</returns>
    IPaymentMethod Approve() { }
}

We've taken away the ability to create a CreditCard, and hidden it in Approve(). The result of Approve() is a parameter to Checkout(). We've proven that the caller must call the prerequisite before the successor.

A simple rearrangement of interfaces can turn a difficult-to-enforce API into one that the compiler can prove. Just recognize which methods are prerequisites of others, and use their returns to call the successors.

MVVM and Linq to SQL the easy way

Sunday, August 9th, 2009

Download the source code and follow along.

The biggest challenge implementing the MVVM pattern is the View Model responding to changes in the Data Model. Usually, the View Model needs to subscribe to the PropertyChanged events fired by the Data Model, update its internal state, and then fire its own PropertyChanged events for the View. That's the hard way.

The easy way is to use Update Controls. With Update Controls, you just decorate the Data Model and wrap the View Model. The library takes care of everything in between.

Decorate a Linq to SQL data model
You decorate the data model by adding Independent sentries to every property. Whenever the property is accessed, call OnGet(). Whenever it is modified, call OnSet(). To decorate a Linq to SQL data model, we just need to inject these calls into the generated code. Unfortunately, the Linq to SQL code generator does not give you a way to easily tweak its output.

Damien Guard has solved that problem. His open source project LINQ to SQL templates for T4 is incredibly easy to set up and start using. It drops straight into Visual Studio and replaces the build-in code generator with one that you control. With just a few edits to his provided T4 template, I replaced INotifyPropertyChanged with Independent sentries.

I added a sentry for each single-valued property, and called OnGet() inside of the getter and OnSet() in the setter. I also added a sentry for each collection. They require OnGet() in the getter, and OnSet() when something is added or removed. Finally, I added a sentry per table, to take care of the top-level queries. For these, I call OnSet() on any insert, update, and delete. The final T4 template is in the example source code.

Wrap the view model for WPF
Wrapping the view model for the view is a one-liner.

public Window1()
{
    InitializeComponent();

    // Create a data model and a navigation model.
    _blog = new Blog();
    BlogNavigationModel navigationModel = new BlogNavigationModel();

    // Put them both in a view model, then wrap it for the view.
    this.DataContext = ForView.Wrap(new BlogViewModel(_blog, navigationModel));
}

We create a view model based on the decorated data model and a similarly decorated navigation model (more on that later). Then we let Update Controls wrap it up before we give it to the view.

View model per scope
NoteworthyThe example application -- Noteworthy -- is a blog editor. Different parts of the view focus on different granularities of data. The main view (shown in red) focuses on the blog as a whole. The article list items and detail pane (shown in blue) focus on a single article. And the tag list items (shown in green) focus on a single tag.

We define one view model class for each of these three scopes. Each one takes constructor parameters to put itself in context. So the BlogViewModel takes a Blog, the ArticleViewModel takes a Blog and an Article, and the TagViewModel takes a Blog and a Tag.

The DataContext property of a WPF control determines where it begins for data binding. If you don't set it, the control inherits it from its parent. If the control is an item in a list, then it is automatically set to an element of the ItemsSource. Finally, you can data bind to a property of its parent scope. Noteworthy uses all three techniques.

Property per control attribute
WPF data binding connects control attributes to object properties. So within each view model, we define a property for each attribute of a control. The view model exists to serve of the view, so this one-to-one mapping is to be expected. The view model is an isolation layer designed to keep these view-specific concerns out of your data model.

Navigation model per user context
The user of your application has a conceptual model of how controls should work together. Noteworthy is a single window program, so they expect all of the controls on that window to interact appropriately. If it had multiple child windows all within a parent, they would expect each window to be its own context, but participate within the context of the main window. And if it were a composite application, they would expect all of the components to work together.

Where the user draws their conceptual boundary, we create a navigation model. A navigation model records the user's point-of-view as they navigate through the application. It keeps track of their selection and temporary input.

All of the state in the navigation model is transient. Nothing is written to the database. Persistent state belongs in the data model. This keeps the view model completely stateless. The view model has only behavior, which depends upon the data model and the navigation model.

Because the navigation model is transient, we just write fields, select them, and hit Ctrl+D, G to generate the properties. For example, here's the property that records the selected article. The part that I wrote by hand is highlighted:

public class BlogNavigationModel
{
    private Article _selectedArticle;

    #region Independent properties
    // Generated by Update Controls --------------------------------
    private Independent _indSelectedArticle = new Independent();

    /// <summary>
    /// The article for which to display details.
    /// </summary>
    public Article SelectedArticle
    {
        get { _indSelectedArticle.OnGet(); return _selectedArticle; }
        set { _indSelectedArticle.OnSet(); _selectedArticle = value; }
    }
    // End generated code --------------------------------
    #endregion
}

The properties of the navigation model are always data model types, never view model types. The view model depends upon the navigation model, not vice-versa.

Explore on your own
That's just enough to get you started. There are many interesting patterns that came together in the making of Noteworthy. Here are some that you might want to examine on your own:

  • The BlogViewModel.Articles property is filtered by the selected tag.
  • The article detail pane is a Grid within a Grid. The outer grid binds IsEnabled, while the inner grid binds DataContext.
  • The TagListBoxStyle resource binds the ListBoxItem.IsSelected attribute to the TagViewModel.TagIsSelected property to support multiple selection.
  • The Blog data model class uses a Dependent sentry to cache Tags.
  • Every view model setter and command that affects the data model calls SubmitChanges.
  • The ArticleViewModel.AvailableTags property uses .Except() to get all tags not assigned to the article.
  • The BlogViewModel.SelectedArticle property wraps the data object from the navigation model in a view model, and then unwraps it on the way back.
  • The first tag in BlogViewModel.Filters is actually not a tag at all. It is a null to represent the lack of a filter.

Expect future posts to return to this example and explore these points in more detail.

Math 101 for developers

Thursday, August 6th, 2009

My development process is deeply mathematical. It is based on the work of Bertrand Meyer and other computer scientists. Math is severely under represented among today's TDD/YAGNI/Agile/anti-BDUF digerati. I like to think through the problem before putting hands to keyboard, and I don't like being told I'm wrong for doing so. So to fill the void, I will be presenting my own mathematically-based process.

When people hear "math", they get scared. They think about how they struggled through trig class, or how statistics kicked their butts. If your palms sweat when you think about geometry, please calm down and bear with me. The math that we are going to talk about is not hard. You just have to think things through logically.

Null reference exceptions
Even though my process begins away from the keyboard, I'll begin this discussion with code. We can work backwards from there.

Your first exercise is to prove that this code does not throw a null reference exception.

class DogLover
{
	public void BuyAPet(Store store)
	{
		Pet pet = store.BuyDog();

		if (pet == null)
			throw new Exception("No dogs were available.");
		else
			pet.Feed();
	}
}

We don't know anything about the BuyDog() method. It may return null or it may not. We have to check the contract. Not many programming languages have a way to encode a contract (Eiffel and Spec# are the only two that come to mind), so we usually rely on comments if we write a contract at all. Here's the contract for BuyDog():

/// <summary>
/// Buy a dog from the pet store.
/// </summary>
/// <returns>A dog if one is available, or null if they are out of stock.</returns>
public Pet BuyDog()
{
	// ...
}

So the contract tells us that BuyDog() could return null. That is its postcondition: the part of the contract that promises what will be true. Based on this postcondition, we cannot prove that pet is not null right after the call.

But we do have a check in the code. As you enter the "else", we know that the "if" condition is false. Therefore, pet != null. pet.Feed() does not throw a null reference exception.

That is the complexity of proof that I'm talking about. It's really easy to do. Much easier than anything you'll see in Euclid's Elements.

Preconditions
Go back and check the code again. There is a case that we did not consider. We proved that pet.Feed() will not throw, but we did not prove that store.BuyDog() is safe. There are two ways to do so: add a check, or add a precondition.

If we add a check before store.BuyDog(), then the same proof as above applies. This is known as "defensive programming". I personally do not like defensive programming because of all the noise it adds to code. I much prefer preconditions.

A precondition is part of the contract. It is a requirement that certain conditions must be true before you call a method. We add a precondition like so:

/// <summary>
/// Buy a pet from a store.
/// </summary>
/// <param name="store">The store from which to buy the pet.
/// Must not be null.</param>
public void BuyAPet(Store store)
{
	// ...
}

Now the caller knows that he cannot pass null.

But this comment did nothing to change the behavior of the code! How does this solve the problem?

The problem is not what the code actually does. The problem is that you couldn't prove that the code worked. Now you can. The BuyAPet() method lives inside of a larger system. If there ever was a problem, you would know that the author of BuyAPet() proved his code; the caller did not. Therefore the bug is in the calling code.

Most compilers do not do this proof for you. Eiffel verifies contracts at run time, not at compile time. Spec# can do some simple proofs at compile time (including this one), but is not yet adopted for commercial use. Still, if dynamic languages can get away with not checking types until run time, I'm fine with not checking contracts until run time. That's what unit tests are for.

Predicates
Let's modify the code a bit.

/// <summary>
/// Buy a pet from a store.
/// </summary>
/// <param name="store">The store from which to buy the pet.
/// Must not be null.</param>
public void BuyAPet(Store store)
{
	if (store.DogsInStock < 1)
		throw new Exception("No dogs were available.");

	Pet pet = store.BuyDog();
	pet.Feed();
}

We've removed the check for pet == null. Can you prove that this code is safe?

You still can if you define a predicate on Store.

/// <summary>
/// The number of dogs available for sale.
/// </summary>
public int DogsInStock { get; private set; }

/// <summary>
/// Buy a dog from the pet store.
/// </summary>
/// <returns>A dog if DogsInStock > 0, or null otherwise.</returns>
public Pet BuyDog()
{
	// ...
	return null;
}

The postcondition on BuyDog() now references the property DogsInStock. We can use that in our proof. Now the steps are:

  1. If we reached store.BuyDog(), then store.DogsInStock < 1 is false.
  2. store.DogsInStock < 1 is false implies store.DogsInStock >= 1 is true.
  3. store.DogsInStock >= 1 implies store.DogsInStock > 0 (since DogsInStock is an integer).
  4. By the precondition of BuyDog(), DogsInStock > 0 implies that a non-null dog is returned.
  5. pet is not null.
  6. pet.Feed() does not throw.

I would usually not spell out these steps so explicitly, but these are the thoughts that go through my mind as I read and write code.

Concurrency
There is one small detail that I conveniently ignored in the prior proof. If we allow for concurrent access of the Store, then the proof is invalid. We cannot rely on DogsInStock > 0 when we get to step 4, since that might be changed in a different thread.

Concurrency is the devil for proofs. Functional languages lend themselves better to proof specifically because they disallow concurrent modification of an object; all objects are immutable, so there is no way that another thread could modify it. But hope is not lost.

In my code, I explicitly call out the parts that could be used concurrently. If nothing is said, then the code is not assumed to be thread-safe. You are therefore not allowed to call it concurrently. Since BuyAPet() makes no promise of thread safety, it would be incorrect to call it on multiple threads without guaranteeing that no other thread is using the same DogLover or Store. It is an implied precondition, but it is still a precondition.

I limit the amount of multi-threaded code in my applications. By the time I get to business logic, I've isolated the object and all of its parameters. They are either immutable or owned by that thread by the time the method is called. This allows me to use proof even in the face of concurency. And I can do so without adding noise to the business code in the form of locks or explicit preconditions.

Proof is a valuable tool in software development. Take a look at the code you wrote today and see if you can prove that it does not reference null.

How to not implement INotifyPropertyChanged

Tuesday, August 4th, 2009

A quick search reveals that many people have tackled the problem of implementing INotifyPropertyChanged. Here are just a few:

Here's my solution: DON'T!

You do not need to implement INotifyPropertyChanged in order to do data binding. Update Controls will do it for you. It works in Winforms, in WPF, and in Silverlight.

What's the problem, anyway?
The problem that most people see with this interface is the "magic string". You have to pass the name of the property in the event args. Many of these solutions use reflection to figure out the property name for you. That solves the magic string problem.

But that's not the real problem!

The real problem is that these techniques only work with independent properties. If one property depends upon another, then these techniques fail.

Take a closer look at PConverse's solution published in Code Project. In his RegularPerson class, DisplayName depends upon both FirstName and LastName. It has no backing field. It has no setter. It is a dependent property.

Look at the extra work he has to do in NotifiablePerson to fire PropertyChanged for DisplayName. He has to explicitly write code in the setters of both FirstName and LastName. Both of these properties have to "know" that DisplayName depends upon them.

That's a backwards dependency. FirstName and LastName should not know about DisplayName; DisplayName knows about FirstName and LastName.

Compare that to the same code using Update Controls.

public class Person
{
    private string _firstName;
    private string _lastName;

    private Independent _indFirstName = new Independent();
    private Independent _indLastName = new Independent();

    public string FirstName
    {
        get { _indFirstName.OnGet(); return _firstName; }
        set { _indFirstName.OnSet(); _firstName = value; }
    }

    public string LastName
    {
        get { _indLastName.OnGet(); return _lastName; }
        set { _indLastName.OnSet(); _lastName = value; }
    }

    public string DisplayName
    {
        get { return FirstName + " " + LastName; }
    }
}

Niether FirstName nor LastName know about DisplayName. The dependencies are one-way and in the correct direction.

Maintainability is the problem
The above example is small and manageable. But imagine what happens when this application grows. Backwards dependencies get out of hand. They cross class boundaries and cause tight coupling between objects. Pretty soon the application is nothing but dependency management.

So please, stop inventing better ways to implement INotifyPropertyChanged. It's like inventing a better way to turn the the crank on the front of a Model T. There is no good way. Just don't do it.