Best practices – Draft

These are my ideas so far around Best Practices for the v1.0 of the Big, Strange, How-To. Let me know what else we might want to say. What have I forgotten? What am I dead wrong about? Let’s work up a great list to help users get Stranger than ever.

My idea of a Strange Best Practice.

My idea of a Strange Best Practice.

General

“Render unto Unity that which is Unity’s”

What is the core purpose of Strange?

Dependency injection? That’s just one of many features. Decoupling? That’s simply a means to an end. No, the core purpose of Strange is to make your life easier. It’s a tool, like any tool. And like any tool it should be applied to the correct set of jobs. However much you like drills, you wouldn’t drive a nail with one. In the end, all the DI/IoC/Decoupling magic has to be judged against that standard. So after giving it a reasonable try, consider every recommendation, “rule”, and philosophical tenet here by evaluating exactly that. I already know the extreme value of what I’m advocating. It won’t hurt my feelings if you disagree (well, maybe a little).

Does the feature you’re working on fit well into Strange’s structure? If you’re trying to rethink physics into a Strange controller because that’s “proper” MVC, then I guarantee you’re doing it wrong. Unity already has a very nice physics system. Let it do its job. A third-party AI designed to be attached to a specific GameObject is more of a grey area. So ask yourself: is this likely to change? Is there a sensible way to decouple (perhaps apply the AI MonoBehaviour as a secondary Mediator)? If you can do these things, make it Strange. If you can’t, you have my permission to let it be.

Injection

Minimize injection at performance-critical moments

Strange is at its best for application-level architecture, and at its worst in your game loop. Supplying Mediators and injections to thousands of enemies at a performance critical moment would not be a very efficient use of resources. Be realistic about your performance requirements. If your game is entirely turn-based, maybe the performance issue is irrelevant to you. If you’re trying to create a massively multiplayer action game that runs on an older smartphone, make sure you think this through!

Wrap a third-party Singleton

Lots of very fine (and some not so fine) third-party libraries are written as Singletons. While we disagree with this choice architecturally, it’s a simple fact of the Unity world. If you need to use a third-party library written as a Singleton, simply wrap it in an interface that is useful to you. We provide instructions on exactly how to do this in the injection deep dive.

If you need named bindings, use supply-binding where possible.

As of v1.0, you can apply named bindings through inversion.

Signals/Commands

Models and services should never listen to Signals

When models and services listen to Signals, they make two critical errors. First, they become tightly coupled to the rules of the specific game/application, which limits portability. Second, they bypass Commands, which tends to result in models and services bloated with logic. Logic is for Commands. Models handle state. Services communicate outside the app.

Dispatching Signals (as opposed to listening) is a bit more of a gray area. It still limits portability, but there’s less of a tendency to bypass Command logic. Stylistically, some people prefer to make Commands completely stateless, so that they are never retained. I prefer the following pattern.

Use Promises in retained Commands

The most common reason for retaining a Command is that you are calling outside Unity, waiting for some response. The new Promises package makes this sort of callback much cleaner and lowers the number of dependencies. It also strengthens the argument for keeping Signals out of services and models altogether.

Consider the following Command:

public class FetchLevelNameCommand : Command
{
    [Inject]
    public IServerAPI service { get; set;}
    override public void Execute()
    {
        Retain();
        service.FetchLevelName().Then(OnName);
    }

    private void OnName(string name)
    {
        //Do something with the result
        Release();
    }
}

What I like about this pattern is (a) it’s very clean, readable code, (b) your service needs no injected Signals, (c) any adapter logic (up, down, or both) gets handled in the same place.

Using clear naming conventions, match Signal names to Commands

Ideally, Signals and Commands should be named such that they obviously connect. Obviously, the entire point of decoupling them is to allow you not to do this. That doesn’t mean you need to set out to make things any harder than they have to be. Start things in alignment, and don’t lose sleep when design changes dictate that they drift.

Pool Commands when possible

Every object created and destroyed must be handled by garbage collection. Marking Commands as Pooled() eliminates some of this collection. Provided you are careful to clean up after yourself, it’s usually best to mark Commands this way. See the section on Command pooling for details.

Follow the single-responsibility principle in your Commands

Treat each Command like a method that does one thing. If a single Signal results in ten different things happening, ganging all that up into one Command makes that code complicated, non-portable and inflexible. On the other hand, if you break all those responsibilities down, it becomes much easier to reuse the bits that may apply to other Signals. You’ll get less code duplication and more reuse, and other programmers (including you in three months’ time) will be able to understand with greater ease what the hell you were trying to accomplish.

Avoid injecting into Views

This is definitely a best practice, not a requirement. When a View goes through the mediation process we explicitly check whether it needs injection. So why tell you not to do a thing that we explicitly allow and provide a mechanism for? This goes to a philosophy that I call “I’m not your mother”.

There are plausible reasons for injecting directly into Views: you might have a bit of decoupled code you want accessible by the View; you might not buy our argument about the value of mediation; perhaps you want to couple a model directly to a View for convenience. I might disagree with these choices (or maybe not, depending on circumstance), but the choice on how to use our tool is ultimately yours.

Now, why is it “best” not to do this? Read the mediation section in the deep dive: keeping your Views as pure Unity probably serves you best most of the time.

“Gang up” a View into a useful component

We often observe that new users misconstrue the purpose of mediation, thinking that each GameObject, each button, each line of text must be its own View with a corresponding Mediator. This is manifestly incorrect. The point of mediation is to allow you a clean separation between the View and the app…nothing in that specifies a 1:1 control:View relationship. Create useful components, small or large, that meet your needs, and mediate those. With UI, I often find that this correlates to 1 screen = 1 View.

Keep Mediators thin

Mediators aren’t intended to do the work of the View. Nor are they intended to provide (much) logic. The sole role of the Mediator is to adapt between View and application. Keep it thin.

Consider using multiple Mediators

It is commonly assumed that each View needs only one Mediator. This is usually sufficient, but there are excellent cases for applying multiple Mediators. My favorite is for analytics. Rather than mixing up your analytics logic (was this button clicked? how many times? before or after the user did this other thing?) with either View or Mediator, consider adding an extra Mediator solely for the purpose of listening out for button clicks and the like. This can make the agonizing process of instrumenting your game clean and trivial.

Contexts

Use clear naming conventions plus implicit bindings to keep Contexts clean

In even a moderately complex application, your Context can start to look like a very long shopping list. We find that many bindings start to crystallize as development proceeds. Rather than enumerate the blindingly obvious, we recommend using Strange’s implicit binding feature to keep your Context spare. Implicit bindings are always overridden by explicit bindings, so you can make changes when special cases arise.

Part of the trick to keeping this easy, especially in large teams, is to maintain sensible naming conventions so you can intuit the most likely implementing class.

//Sensible naming convention
public class PlayerModel : IPlayerModel

//Less sensible naming convention
public class PlayerModel : IPlayer

//Full-on insanity naming convention
public class DefaultPlayerModel : IShip

Build platform variants at the Class level; put all platform-dependent statements in the Context

Have you ever waded through code like this?

public class FaceRegognizer {
 void Start () {


    #if UNITY_EDITOR
     Debug.Log("Doing a thing in Unity Editor");
    #elif UNITY_IPHONE
     Debug.Log("Doing a thing on Iphone");
    #elif UNITY_STANDALONE_OSX
    Debug.Log("Doing a thing on Stand Alone OSX");
    #else UNITY_STANDALONE_WIN
     Debug.Log("Doing a thing on Stand Alone Windows");
    #endif
 }        
}

And let’s be honest: it’s NEVER that clean. Instead of having this code mixed-in and littered throughout your code, we recommend sectioning off all platform-specific behavior into well-named classes. You’ll still have all that #if…#elif magic, but it’ll all be in one place, and each implementing class will tell a consistent, readable story for its use case.

//All common capabilities in an abstract class
abstract public class AbstractFaceRecognizer : IFaceRecognizer{}

//Each of these implements the functionality as required by the platform
public class DefaultFaceRecognizer : AbstractFaceRecognizer{}
public class IosFaceRecognizer : AbstractFaceRecognizer{}
public class WindowsFaceRecognizer : AbstractFaceRecognizer{}
public class OSXFaceRecognizer : AbstractFaceRecognizer{}

//Now bind with all the #iffyness
#if UNITY_EDITOR
injectionBinder.Bind<IFaceRecognizer>().To<DefaultFaceRecognizer>().ToSingleton();
#elif UNITY_IPHONE
injectionBinder.Bind<IFaceRecognizer>().To<IosFaceRecognizer>().ToSingleton();
#elif UNITY_STANDALONE_OSX
injectionBinder.Bind<IFaceRecognizer>().To<OSXFaceRecognizer>().ToSingleton();
#else UNITY_STANDALONE_WIN
injectionBinder.Bind<IFaceRecognizer>().To<WindowsFaceRecognizer>().ToSingleton();
#endif

Stick to a strict, Context-based hierarchy

As we explained in the Mediation deep-dive, the Context-based hierarchy — i.e., childing a GameObject to a chain that can “bubble up” to a ContextView — is only required in a multi-Context situation. That said, starting this way, even in a single-Context situation, gives you the flexibility to choose the multi-Context approach at a later time.

Build modularity using multiple scenes, each in its own Context

One of Strange’s superpowers is connecting multiple Contexts. We often talk about it as a special use-case, but the utility of this concept shouldn’t be underestimated, especially when working in teams. This basically brings decoupling to the macro level, since one person/team can work on one chunk of a game, completely abstracted from others. A scene can be a mini-game, level, store, UI screen, what-have-you, built to stand all on its own, yet able to be integrated into the larger whole when necessary. This can greatly speed up workflow and even make it possible to share whole sections of a project between multiple games. I mean, seriously, once you’ve created an in-game store for Age of Jellyfish, do you really want to build it again for Age of Anchovies?

Advertisements

6 thoughts on “Best practices – Draft

  1. Yep, good start ! However, I would like to find best practices and real life examples for :
    – avoid overmediating
    – using views and subviews
    – the optimized way to double data binding
    – when using static binding vs runtime binding with json
    – make a factory to build/rebuild a game scene after a save/load to retrieve object

    What do you think ?

  2. Very good writeup.

    Its hard to say if its complete as a best practice on the 10000 foot level as the things I am be missing (going into similar directions as Baca Sable’s) are one layer ‘deeper’ into the detail direction and might or might not be covered in other places.
    One thing I would though love to see on the this ‘high level best practices’ is related to meaningful separations of ‘view vs service’ in MVCS especially with 3rd party library and UnityEngine.Component descendants in the mix, where its often not that simple anymore to separate it.

    • It’s a little difficult to know how detailed this best practices chapter should be. I’ve erred on the more general side, partially so that we are giving useful information to the most users, but also because I get less certain as the use cases get more complex. That *I* do a thing a certain way doesn’t always make it a best practice!

      I’ll think about Baca’s suggestions and yours — and shop them around with others — to see what the right balance is. As always, we hugely appreciate the input!

      • I fully understand that. Thanks to Stranges wide field of application, including the optional MVCS, I wouldn’t be in a better position to even decide whats a general good practice and context one as I primarily used Strange with MVC in the old days when it first appeared on git and with MVCS now, as such its highly appreciated that you guys are putting this much effort into it.

  3. Pingback: v1.0 and BSHT latest | StrangeIoC

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s