Member Design Guidelines

Edit this page

Allow properties to be set in any order (RG1100)

Properties should be stateless with respect to other properties, i.e. there should not be a difference between first setting property DataSource and then DataMember or vice-versa.

Use a method instead of a property (RG1105)

  • If the work is more expensive than setting a field value.
  • If it represents a conversion such as the Object.ToString method.
  • If it returns a different result each time it is called, even if the arguments didn’t change. For example, the NewGuid method returns a different value each time it is called.
  • If the operation causes a side effect such as changing some internal state not directly related to the property (which violates the Command Query Separation principle).

Exception: Populating an internal cache or implementing lazy-loading is a good exception.

Don’t use mutually exclusive properties (RG1110)

Having properties that cannot be used at the same time typically signals a type that represents two conflicting concepts. Even though those concepts may share some of their behavior and states, they obviously have different rules that do not cooperate.

This violation is often seen in domain models and introduces all kinds of conditional logic related to those conflicting rules, causing a ripple effect that significantly increases the maintenance burden.

A property, method or local function should do only one thing (RG1115)

Similarly to rule RG1000, a method body should have a single responsibility.

Avoid static methods (RG1120)

Avoid static methods. They can introduce dependencies which makes it difficult to substitute alternative implementations (e.g. tests or implementations).

Exception: static methods are fine for sharing pure utility code where the implementation will not be substituted and the code is only a handful of lines. Path.Combine is a good example, whereas File.Copy is not.

Don’t expose stateful objects through static members (RG1125)

A stateful object is an object that contains many properties and lots of behavior behind it. If you expose such an object through a static property or method of some other object, it will be very difficult to refactor or unit test a class that relies on such a stateful object. In general, introducing a construction like that is a great example of violating many of the guidelines of this chapter.

A classic example of this is the HttpContext.Current property, part of ASP.NET. Many see the HttpContext class as a source of a lot of ugly code. In fact, the testing guideline Isolate the Ugly Stuff often refers to this class.

Return an IEnumerable<T> or ICollection<T> instead of a concrete collection class (RG1130)

You generally don’t want callers to be able to change an internal collection, so don’t return arrays, lists or other collection classes directly. Instead, return an IEnumerable<T>, or, if the caller must be able to determine the count, an ICollection<T>. You can also use IReadOnlyCollection<T>, IReadOnlyList<T> or IReadOnlyDictionary<TKey, TValue>.

Exception: Immutable collections such as ImmutableArray<T>, ImmutableList<T> and ImmutableDictionary<TKey, TValue> prevent modifications from the outside and are thus allowed. It is also ok to declare private methods as returning a concrete collection (e.g. List<T>) as it is often helpful for the caller to use the real type rather than the interface type (e.g. to avoid having to call .ToList() on the returned interface).

Avoid null (RG1135)

We avoid null. We only use null when unavoidable (for example, consuming a third-party API). In our own code we rigorously defend against null by:

  • Using an empty collection or an empty string instead of a null reference.
  • Using the null object pattern to represent the absence of something (e.g. NullLogger rather than a null reference). This works when the null object is Liskov-substitutable for the real thing.
  • Explicitly naming things that could be null (eg loggerOrNull) in places where the other options don’t work.

When reading Redgate code you should be able to assume everything is non-null unless otherwise specified.

We eagerly await the release of C# 8 so this can be made explicit in the code rather than relying on names.

Note:

  • We avoid annotations such as JetBrains [NotNull] as C# 8 is around the corner and we will adopt this as soon as possible.
  • We also don’t want to introduce Maybe/Option type, because of unfamiliarity with that functional programming concept. We’ve got a strong belief that native C#8 non-nullable reference is good enough and a better option for C# developers.

Define parameters as specific as possible (RG1137)

If your method or local function needs a specific piece of data, define parameters as specific as that and don’t take a container object instead. For instance, consider a method that needs a connection string that is exposed through a central IConfiguration interface. Rather than taking a dependency on the entire configuration, just define a parameter for the connection string. This not only prevents unnecessary coupling, it also improves maintainability in the long run.

Note: An easy trick to remember this guideline is the Don’t ship the truck if you only need a package.

Consider using domain-specific value types rather than primitives (RG1140)

Instead of using strings, integers and decimals for representing domain-specific types such as an ISBN number, an email address or amount of money, consider creating dedicated value objects that wrap both the data and the validation rules that apply to it. By doing this, you prevent ending up having multiple implementations of the same business rules, which both improves maintainability and prevents bugs.