How to avoid global state here?

Started by
4 comments, last by frob 10 years, 6 months ago

Now I realize that having global state (particularly of the mutable variety) is a bad idea, but I've had difficulties in the past getting around it for some applications.

Some time ago I had this problem with logging, but I've gotten around to the idea of just using a free function, because logging doesn't really require any kind of persistent state. Now I have a new problem, and it's logging's ornery younger brother: debug printing.

Here's what I'm using now:


using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;

namespace Foo
{
    public static class DebugPrinter
    {
        private static StringBuilder debugString = new StringBuilder();
        private static List<string> idList = new List<string>();

        [Conditional("DEBUG")]
        public static void RegisterString(String str, String id)
        {
            if (!idList.Contains(id))
            {
                debugString.Append(str).AppendLine();
                idList.Add(id);
            }
        }

        [Conditional("DEBUG")]
        public static void Flush()
        {
            debugString.Clear();
            idList.Clear();
        }
        [Conditional("DEBUG")]
        public static string getString()
        {
            return debugString.ToString();
        }
    }
}

Usage is pretty clear: whatever class wants to write to it calls registerString(passing an id, because I'm using the sharpDX toolkit and update and draw aren't synchronized with each other) and the reader calls getString, followed by flush, then renders the string to the screen.

Now, if you feel the urge to stab me in the eye for this, I fully accept that. It's probably the nastiest thing in my whole code base. I just can't think of a way around it. I can't use a free function because I need to mantain state between draw calls. I could make some heavyweight scaffolding around deep dependency-passing down my state hierarchy.

I'm aware that it's kind of one of those leading sorts of questions(how do I keep all this shared global state without any shared global state), but it's also a design problem I don't know any other solution for.

Advertisement


I just can't think of a way around it. I can't use a free function because I need to mantain state between draw calls. I could make some heavyweight scaffolding around deep dependency-passing down my state hierarchy.

Single responsibility principle.

Have one class that accepts listeners for logging info.

Have another class that listens to logging info and displays it to screen.

Have another class that listens to logging info and dumps it to a file.

And since this is C#, reuse the functionality already present. Creating a trace listener is easy. Creating trace filters is easy. Adding a few utility functions that automatically specify the appropriate filters to use is not bad either. All that is left is adding a trace listener that displays new lines on the screen, and you're done.

So this tracing functionality is awesome and I didn't know about any of it before (except trivial Debug.WriteLine stuff). I'll have to do a bit of string manipulation to handle duplicates, but it's a small price to pay for all this bling. Thanks for the heads up!

Now that I've had a chance to mess around with the tracing stuff...

I'm still having trouble handling de-duplication. I've tried a few different designs, but none of them seem to work quite right. Suppose for simplicity I have two objects -- the renderer and a camera class. The camera needs to be able to log camera position every update cycle, but neither the camera or the renderer know about the other directly.

This is the only thing I've thought of thus far:


class DebugPrintTraceListener : TraceListener
{

    //need to reparse hashcodes and check against a dictionary that gets flushed on read.
    public override void Write(string message);
    public override void WriteLine(string message);
    public string readAndFlush();
}

class DebugPrintObject
{
    string message;
    int hashCode;

    public DebugPrintObject(Object parent, string message)
    {
        hashCode = parent.GetHashCode();
        this.message = message;
    }

    public string ToString()
    {
        return hashCode.ToString() + " " + message;
    }
}

//somewhere in the renderer's initialization -- or containg class, whatever
System.Diagnostics.Debug.Listeners.Clear();
System.Diagnostics.Debug.Listeners.Add(myMemberListener);

//Somewhere in the camera
System.Diagnostics.Debug.WriteLine( new DebugPrintObject(this, "stuff"));


This should technically work, but the debugging objects are pretty much the definition of disgustingly hacky.

The solution would be easy if I only needed to log, but because of the deduplication problem I need some way of knowing the sender of the tracing data. The diagnostics stuff doesn't seem to support this, or really supporting anything but sending strings along. Sure, there's filters on both sides I can use, but it's mostly pointless if the only communication format is a string.

EDIT: It's worth mentionining that, after all this crap, I'm still using global state, as System.Diagnostics.Debug is just as global and stateful as my old ad-hoc solution, albeit slightly less abstracted.

I thought of another idea: Put two listeners on System.Diagnostics.Debug.Listeners, we'll call them sender and receiver. register a TraceSource owned by the renderer with the receiver, and register other TraceSources in every class that wants to write information with a sender. Every update cycle, the sender owners cache their string data, but don't send. When the receiver needs to draw, it sends a trace to the senders. The senders notify their parents, which then call back to the senders to write to the receiver with their data. This works, doesn't technically use global state (though it leverages global state to set up), doesn't require weird encodings, but holy hell is it incredibly roundabout. This can't be the best way to go about it.

SeraphLance,

I think you might be getting stuck in dogma; sure global state isn't delightful, but its better than the alterative method you've got round to designing (in my opinion). Tracing in C# has a CorrellationManager so you can do correllation of messages from the same class instance. Isn't that what you are after here ?

Phillip

SeraphLance,

I think you might be getting stuck in dogma; sure global state isn't delightful, but its better than the alterative method you've got round to designing (in my opinion). Tracing in C# has a CorrellationManager so you can do correllation of messages from the same class instance. Isn't that what you are after here ?

Phillip

Indeed, there is a reason for the rules.

It is true that global state should be avoided. It can create non-local dependencies. It can lack access control. It can create memory allocation issues. It can make automated testing much harder.

The global object has a single responsibility: forward messages to listeners. The only global state it maintains is the list of listeners, but this does not modify behavior to the class users.

Note that the Debug.Trace functionality does not suffer from the problems of most global objects. This type of "data distribution service" is one area where global objects are reasonably safe, when implemented correctly.

Data distributors and event broadcasters can spread data out, but those are not non-local dependencies; other systems will not be modifying it behind your back in potentially harmful ways. It does provide access control because of the way methods are used. The language's Trace functionality doesn't create memory allocation issues because it comes from the system itself. And it doesn't interfere with automated testing because the developer can control the listeners during the test.

You need to understand the guidelines and rules so you know when it is acceptable to break them.

This topic is closed to new replies.

Advertisement