58

Let's say there is a member SomeMethod in an interface ISomeInterface as follows:

public interface ISomeInterface
{
    int SomeMethod(string a);
}

For the purposes of my program, all consumers of ISomeInterface act upon the assumption that the returned int is greater than 5.

Three ways come to mind to solve this -

1) For every object that consumes ISomeInterface, they assert that the returned int > 5.

2) For every object that implements ISomeInterface, they assert that the int they're about to return is > 5.

Both the above two solutions are cumbersome since they require the developer to remember to do this on every single implementation or consumption of ISomeInterface. Furthermore this is relying upon the implementation of the interface which isn't good.

3) The only way I can think to do this practically is to have a wrapper that also implements ISomeInterface, and returns the underlying implementation as follows:

public class SomeWrapper : ISomeInterface
{
    private ISomeInterface obj;

    SomeWrapper(ISomeInterface obj)
    {
        this.obj = obj;
    }

    public int SomeMethod(string a)
    {
        int ret = obj.SomeMethod("hello!");
            if (!(ret > 5))
            throw new Exception("ret <= 5");
        else
            return ret;
    }
}

The problem though now is that we're again relying on an implementation detail of ISomeInterface via what the SomeWrapper class does, although with the benefit that now we've confined it to a single location.

Is this the best way to ensure an interface is implemented in the expected manner, or is there a better alternative? I understand interfaces may not be designed for this, but then what is the best practice to use some object under the assumption that it behaves a certain way more than what I can convey within its member signatures of an interface without needing to do assertions upon every time it's instanced? An interface seems like a good concept, if only I could also specify additional things or restrictions it's supposed to implement.

user4779
  • 929
  • 1
  • 6
  • 13
  • Note that even if `!(ret > 5)`, it is possible that `ret == 5`. – Solomon Ucko Feb 02 '20 at 23:55
  • 21
    What you are discovering is that first, *interfaces are a lousy implementation of the contract concept*, and second *C# does not have dependent types*. Even if your type system problem was solved, you'd still have the first problem. Interfaces make it easy to express contracts like "I have three methods Open, Transmit and Close" but do not make it easy to express "Transmit must be called after Open but before Close..." and so on. – Eric Lippert Feb 03 '20 at 18:52
  • 1
    Is this example coming out of the blue or is there a real explanation or use case to it? just curious – glace Feb 04 '20 at 06:09
  • 1
    @glace out of the blue as a simple toy example that encapsulates my question, but I had real use cases where I needed to assert more things in an interface than what's provided via the method signature. Thankfully Euphoric's answer works well. – user4779 Feb 04 '20 at 06:37
  • @EricLippert "Transmit must be called after Open" is easy enough to encode in types/interfaces by having `open` be the only method of `UnopenedFoo`, returning a `Foo` with `transmit` and `close`. Using types to prevent calling `transmit` after `close` is much harder, e.g. needing linear/affine/uniqueness types. Most languages do this with scope instead, e.g. putting `close` logic in a destructor, or both `open` and `close` logic in a `withFoo` function/method taking a callback. – Warbo Feb 05 '20 at 14:50
  • @Warbo: "Easy enough"? OK, how do we encode the rule "an unopened foo may not be opened *twice* unless it has been previously closed *once*"? We need linear types all the way through to solve the problem in general! The solution "just make a more complicated type system" is expensive, difficult, and not going to happen any time soon, and even if it did, it would *still* be the case that interfaces do not easily encode a great many restrictions on program behaviour that we'd like to describe. – Eric Lippert Feb 05 '20 at 15:51
  • @Warbo: This is reinforced by your note that because we cannot encode even the basic operation of a simple machine like a *door* -- a door cannot be opened once it is open or closed once it is closed, and cannot be passed through unless it is open -- in interfaces, we choose to identify and then "bless" some basic patterns and enforce their rules in the language design itself. – Eric Lippert Feb 05 '20 at 15:57
  • @EricLippert There are languages with such type systems, say, Rust: http://cliffle.com/blog/rust-typestate/ . – Joker_vD Feb 06 '20 at 09:39
  • @Joker_vD: Right, that's an example of the linear type system that Warbo alluded to previously. – Eric Lippert Feb 06 '20 at 15:07
  • @EricLippert Yes allowing re-opening/avoiding double-opening is too hard for such simple types (I've seen the door example solved in Idris though). A common scenario where they *are* sufficient is avoiding expensive initialisation, e.g. a Web server with many request handlers (redirect, login, etc.) where only some need a DB. Giving handlers a `ConnectedDB` is wasteful if it's not needed; giving a `DB` and expecting them to `connect` before `query` is dangerous; giving an `UnconnectedDB` forces them to `connect` to get access to `query`. `close` could be a destructor after response is returned – Warbo Feb 06 '20 at 17:52

8 Answers8

165

Instead of returning an int, return a value object that has the validation hard-coded. This is a case of primitive obsession and its fix.

// should be class, not struct as struct can be created without calling a constructor
public class ValidNumber
{
    public int Number { get; }

    public ValidNumber(int number)
    {
        if (number <= 5)
            throw new ArgumentOutOfRangeException("Number must be greater than 5.")
        Number = number;
    }
}

public class Implementation : ISomeInterface
{
    public ValidNumber SomeMethod(string a)
    {
        return new ValidNumber(int.Parse(a));
    }
}

This way, the validation error would happen inside the implementation, so it should show up when developer tests this implementation. Having the method return a specific object makes it obvious that there might be more to it than just returning a plain value.

Joshua Taylor
  • 1,610
  • 11
  • 11
Euphoric
  • 36,735
  • 6
  • 78
  • 110
  • 9
    This is a good suggestion, but it requires changing the return type. This would be pretty impactful to existing clients of your interface. If you want a similar approach that still allows you to return `int`, check my answer. – Ryan Palmer Feb 02 '20 at 07:20
  • 2
    This way `ValidNumber` is immutable, can only be setted one. If the need of a mutable version is needed, or you really need to use a struct, the validation check can be performed in the setter, instead of the constructor. – bracco23 Feb 02 '20 at 08:21
  • 4
    @bracco23 Immutability is a valuable property in this case. And the struct still won't work, as it is possible to create an empty struct instance with all set to zero. – Euphoric Feb 02 '20 at 08:42
  • 9
    @user4779: This is *one* solution for *some* cases, you should definitely not start to use it now each and everywhere, especially not when it requires to wrap all simple types into some helper class. Instead, inform yourself about [Design by Contract in C#](https://stackoverflow.com/questions/260817/design-by-contract-in-c-sharp) – Doc Brown Feb 02 '20 at 08:54
  • 11
    @DocBrown Design by Contract doesn't seem to be going strong with current versions of C#. While a useful pattern, it is not something you see often, if not at all. – Euphoric Feb 02 '20 at 09:04
  • 1
    @DocBrown I googled about code contracts and found out about DbC and now am intrigued. This seems like a very interesting solution. From what I can gather though this was for some reason discontinued natively in the framework? https://docs.microsoft.com/en-us/dotnet/framework/debug-trace-profile/code-contracts?redirectedfrom=MSDN What would you recommend these days? I like both Euphoric's solution and the DbC thinking, but am also too inexperienced to be able to differentiate when either/or would be needed, and why one might be better than the other in certain situations. – user4779 Feb 02 '20 at 10:59
  • 1
    @Euphoric, very good point re that design flaw of structs. Of course, classes then suffer from the null problem, so they don’t fully address the issue either. C# 8 and nullable ref types would help. Alternatively the number could be validated on read, but then you lose track of where it was incorrectly assigned. – David Arno Feb 02 '20 at 11:45
  • 1
    @DavidArno C# 8 will definitely help, but not because of nullable types, more because they actually added much more support for readonly, especially to be able to create immutable structs . See for instance https://stackoverflow.com/questions/6063212/does-using-public-readonly-fields-for-immutable-structs-work – Pac0 Feb 02 '20 at 16:33
  • 2
    @RyanPalmer: If it shouldn't have been an `int`, but it was mistakenly made one in the past, that's not a reason to just give up. Sure, there may be particular scenarios where breaking an interface can't be done willy nilly; but that's not a reason to discount this as the correct answer to the general question - not a question specific to a particular company's situation. – Flater Feb 02 '20 at 22:53
  • 1
    @Flater Sure, if you make the wrong decision early on, you shouldn't just give up and learn to live with it. However, it depends on the situation. Changing the return type from `int` to an object might be trivial for the OP. But it could also be a nightmare if that interface is used extensively in a large codebase. So, YMMV with both answers. One sure downside with the `ValidNumber` approach is that all clients of this interface are now coupled with that class too. Being coupled with an `int` is much less of a maintenance concern. – Ryan Palmer Feb 02 '20 at 23:03
  • 2
    @RyanPalmer: The premise of the question is that the interface _requires_ the inherent `>5` validation. Implementing an interface makes you responsible for adhering to its requirements. That is what it means to have a contract. If you argue that it should be opt-in, i.e. to be decided by each implementer individually, then you're answering a question that was not asked here. – Flater Feb 02 '20 at 23:17
  • @Flater No, that's not at all what I'm saying. Not sure how you interpreted my response that way. You can return an `int` and still guarantee that contract is honored by all implementers. I demonstrated that in my answer. My point is that changing the return type from a primitive to a more robust object has additional "baggage". Maybe the baggage is worth it, maybe not. – Ryan Palmer Feb 02 '20 at 23:38
  • 2
    Nice answer, but please throw an `ArgumentOutOfRangeException` instead. – helb Feb 03 '20 at 09:09
  • When your language allows it, then you might want to mark the class as sealed/final/whatever prevents people from inherenting from it. Otherwise I could create a class inherenting from it which doesn't do the value validation. – Philipp Feb 03 '20 at 09:38
  • @Philipp Good point. But in this case, it is not necessary, as it is not possible to bypass the constructor in child class, and the property is read-only. – Euphoric Feb 03 '20 at 09:41
  • 1
    On the issue of breaking existing clients - I think that this point is moot, because technically you are adding a new post-condition to the interface and you are therefore "breaking" them (since previously a client could implement this without regard to the new condition, they are now "breaking" the contract and will have to update their implementations regardless). Given the lack of a decent native annotation system (.Net Code Contracts afaik is in limbo right now) I think this solution is quite reasonable. – Stephen Byrne Feb 03 '20 at 10:28
  • 1
    @RyanPalmer Nothing wrong with returning a new type - you just need to add `public static implicit operator int(ValidNumber d) => d.Number;` so that things that are expecting an int can do the conversion. – UKMonkey Feb 03 '20 at 17:16
  • @UKMonkey True, since OP is using C#, operator overloading reduces the downside considerably. – Ryan Palmer Feb 03 '20 at 17:32
  • 1
    @StephenByrne makes an excellent point. If you have to make a breaking change to the contract, it's always better to break compile. Otherwise, a non-compliant implementation *might* crash at runtime, if you're really lucky... – StackOverthrow Feb 04 '20 at 19:35
  • @RyanPalmer The baggage, which is that the number must be greater than 5, was there to begin with. The `ValidNumber` class makes it obvious that it's there. – hegel5000 Feb 04 '20 at 21:48
25

To complement the other answers, I'd like to partially comment on the following note in the OP by providing a broader context:

An interface seems like a good concept, if only I could also specify additional things or restrictions it's supposed to implement.

You are making a good point here! Let us consider on which levels we can specify such restrictions (constraints):

  1. in the language's type system
  2. via meta annotations internal or external to the language and external tools (static analysis tools)
  3. via runtime assertions — as seen in other answers
  4. documentation targetted at humans

I elaborate on every item below. Before that, let me say that the constraints get weaker and weaker the more you transition from 1 to 4. For example, if you only rely on point 4, you are relying on developers correctly applying the documentation, and there is no way anyone is able to tell you whether those constraints are fulfilled other than humans themselves. This, of course, is much more bound to contain bugs by the very nature of humans.

Hence, you always want to start modelling your constraint in point 1, and only if that's (partially) impossible, you should try point 2, and so on. In theory, you always would like to rely on the language's type system. However, for that to be possible you would need to have very powerful type systems, which then become untractable — in terms of speed and effort of type checking and in terms of developers being able to comprehend types. For the latter, see Is the Scala 2.8 collections library a case of “the longest suicide note in history”?.

1. Type System of the Language

In most typed (OO-flavored) languages such as C# it is easily possible to have the following interface:

public interface ISomeInterface
{
  int SomeMethod(string a);
}

Here, the type system allows you to specify types such as int. Then, the type checker component of the compiler guarantees at compile time that implementors always return an integer value from SomeMethod.

Many applications can already be built with the usual type systems found in Java and C#. However, for the constraint you had in mind, namely that the return value is an integer greater than 5, these type systems are too weak. Indeed, some languages do feature more powerful type systems where instead of int you could write {x: int | x > 5}1, i.e. the type of all integers greater than 5. In some of these languages, you also need to prove that as an implementor you always really return something greater than 5. These proofs are then verified by the compiler at compile time as well!

Since C# does not feature some types, you have to resort to points 2 and 3.

2. Meta Annotations internal/external to the Language

This other answer already provided an example of meta annotations inside the language, which is Java here:

@NotNull
@Size(min = 1)
public List<@NotNull Customer> getAllCustomers() {
  return null;
}

Static analysis tools can try to verify whether these constraints specified in the meta annotations are fulfilled or not in the code. If they cannot verify them, these tools report an error.2 Usually, one employs static analysis tools together with the classic compiler at compile time meaning that you get constraint checking at compile time as well here.

Another approach would be to use meta annotations external to the language. For example, you can have a code base in C and then prove the fulfillment of some constraints in a totally different language referring to that C code base. You can find examples under the keywords "verifying C code", "verifying C code Coq" among others.

3. Runtime Assertions

At this level of constraint checking, you outsource the checking from compile and static analysis time completely to runtime. You check at runtime whether the return value fulfills your constraint (e.g. is greater than 5), and if not, you throw an exception.

Other answers already showed you how this looks code-wise.

This level offers great flexibility, however, at the cost of deferring constraint checking from compile time to runtime. This means that bugs might get revealed very late, possibly at the customer of your software.

4. Documentation Targetted At Humans

I said that runtime assertions are quite flexible, however, still they cannot model every constraint you could think of. While it's easy to put constraints on return values, it's for instance hard (read: untractable) to model interaction between code components as that would require some kind of "supervisory" view on code.

For example, a method int f(void) might guarantee that its return value is the current score of the player in the game, but only as long as int g(void) has not been called superseeding the return value of f. This constraint is something you probably need to defer to human-oriented documentation.


1: Keywords are "dependent types", "refinement types", "liquid types". Prime examples are languages for theorem provers, e.g. Gallina which is used in the Coq proof assistant.

2: Depending on what kind of expressiveness you allow in your constraints, fulfillment checking can be an undecidable problem. Practically, this means that your programmed method fulfills the constraints you specified, but the static analysis tool is unable to prove them. Or put differently, there might be false negatives in terms of errors. (But never false positives if the tool is bug-free.)

ComFreek
  • 389
  • 2
  • 8
  • 3
    Mind, you can still use the type system of C#, it just isn't as simple as saying `0..5` as in something like Pascal. You can always make your own type that captures an integer and only allows the values you want - not as convenient, but still works well for correctness. – Luaan Feb 03 '20 at 08:55
  • 1
    @Luaan You mean creating a new class, say `FiveUppedInt`, and use for example `new FiveUppedInt(0)` and `new FiveUppedInt(20)` to represent 5 and 25, respectively? – ComFreek Feb 03 '20 at 08:59
  • 1
    That would probably be a bit too confusing. I mean just making sure you can't create a value of `FlobberCount` smaller than five. You can't rely on the compiler enforcing anything on the interface where you create `FlobberCount`, but anyone who gets an instance of `FlobberCount` knows it must be five or more. You only decide what to do with invalid values on the interface, rather than having that decision spread throughout your codebase (potentially including 3rd parties). The interface itself is tight. – Luaan Feb 03 '20 at 09:05
  • @Mark In fact I did mean "probably" because it depends on the kind of constraints you allow. I reworded it a bit such that it does not cause confusion anymore, I hope. – ComFreek Feb 04 '20 at 07:58
  • If the humans don't read the documentation to find out what interface methods are supposed to do, but instead simply adjust their code until it compiles, is the resulting interface implementation going to be very useful? Human-readable documentation needs to be the primary indication of what interface methods are supposed to do, because if humans follow them the automated checks will be largely redundant, if if humans don't follow them the results will likely be meaningless whether or not they pass validation. – supercat Feb 04 '20 at 22:55
  • 1
    @supercat I largely agree with your comment except for the part: "if humans follow them the automated checks will be largely redundant". Think of the usually listed advantages of typed languages. If humans could always follow docs, they wouldn't need types and Java wouldn't need NPEs. Human-readable docs need to be the primary source for humans, but for computers automatically verifiable constraints need to be the primary source (= main point of my post). And that often start with the type system. – ComFreek Feb 05 '20 at 08:04
  • 1
    @ComFreak I was going to suggest your `FiveUppedInt`, although it should be `SixUpped` and wrap an `unsigned int` (i.e. natural number) to prevent `FiveUppedInt(-5)`. I've seen this approach called "correct by construction", since the invariant is guaranteed to be satisfied due to the nature of the representation (no need for separate proofs/justifications; less need for dependent types). Other examples are `NonEmptyList` pairing a `T` (first element) with a `List` (the rest); or a sorted list of numbers where each element is the (non-negative) difference from the previous element. – Warbo Feb 05 '20 at 15:13
  • @ComFreek: The fact that something is redundant doesn't mean it is totally without value. Redundancy is a very important concept in engineering disciplines. On the other hand, anything that would need to be said about an interface, should be said to the humans who will be implementing an interface even if it is *also* said to the machines that will be processing the implementation. – supercat Feb 05 '20 at 15:31
23

You're trying to design by contract, where that contract is that the return value must be greater than 5. Unfortunately, if you're relying on an interface, the only contract you have is the method signatures.

I'd suggest using an abstract class instead. Here's an approach I would take in Java:

public abstract class SomeAbstraction {
    public final int someMethod(String a) {
        // You may want to throw an exception instead
        return Math.max(6, someAbstractMethod());
    }

    protected abstract int someAbstractMethod();
}

As long as your sub-classes are isolated in a separate package (where someAbstractMethod would be inaccessible) then clients of this abstraction will only have access to someMethod and can safely rely on the return value always being greater than 5. The contract is enforced in one place, and all sub-classes must adhere whether they know it or not. Using the final keyword on someMethod has the added benefit of preventing sub-classes from forcibly breaking that contract by overriding it.

Note: Typically, a contract violation should be exceptional behavior. Thus, you'd probably want to throw an exception or log an error instead of just forcing the return value. But this depends entirely on your use case.

Ryan Palmer
  • 506
  • 2
  • 9
  • 37
    If the called method returns 4, something is wrong and needs fixing. If you silently change it to six, something is still wrong and needs fixing, the caller just doesn’t know. – gnasher729 Feb 02 '20 at 09:31
  • 3
    @gnasher729 Right, `someMethod` should throw in that case. – bdsl Feb 02 '20 at 10:43
  • @bdsl not necessarily. The caller might not need to know eg because it’s the other side of an API and letting an app exception out through that API might be a security risk. So you may just want to log the failure internally and rerun a default value. Exceptions are useful but they aren’t always the answer. – David Arno Feb 02 '20 at 14:14
  • 3
    @gnasher729: It's contextual. Silent alterations may be acceptable, e.g. rounding a decimal to a certain precision or altering a string. The wrapper may exist explicitly to quietly enforce some alterations (e.g. a profanity filter). You're right that there are cases where you'd prefer for it to blow up specifically to point out an error, but it's equally possible that you want graceful handling and immediate alteration. Context is key. – Flater Feb 02 '20 at 23:20
  • 3
    The issue with this is that your thing is now a class, rather than an interface; and THIS will break things that want to use it. For example; now they can't implement this and inherit something else. – UKMonkey Feb 03 '20 at 17:18
  • @UKMonkey Yeah, that's certainly the biggest problem with this approach. Inheritance is rarely the best choice. – Ryan Palmer Feb 03 '20 at 17:29
1

You may have validating annotations that restrict the aceptable returned values. Here is an example in Java for Spring taken from baeldung.com but in C# you have a similar feature:

   @NotNull
   @Size(min = 1)
   public List<@NotNull Customer> getAllCustomers() {
        return null;
   }

If you use this approach you must consider that:

  • You need a validation framework for your language, in this case C#
  • Adding the annotation is just one part. Having the annotation validated and how this validation errors are handled are the important part. In the case of Spring it creates a proxy using dependency injection that would throw a Runtime ValidationException
  • Some annotations may help your IDE to detect bugs at compile time. For example, if you use @NonNull the compiler can check that null is never returned. Other validations needs to be enforced at runtime
  • Most validation frameworks allow you to create custom validations.
  • It is very useful for processing input data where you may need to report more than one broken validation at the same time.

I do not recommend this approach when the valdiation is part of the business model. In this case the answer from Euphoric is better. The returned object will be a Value Object that will help you create a rich Domain Model. This object should have a meaningful name with the restrictions acording to the type of business you do. For example, here I can validate that dates are reasonables for our users:

public class YearOfBirth

     private final year; 

     public YearOfBirth(int year){
        this.year = year;    
        if(year < 1880){
            throw new IllegalArgumentException("Are you a vampire or what?");
        }  
        if( year > 2020){
            throw new IllegalArgumentException("No time travelers allowed");     
         }
     }
}

The good part is that this kind of object can attract very small methods with simple and testeable logic. For example:

public String getGeneration(){
      if( year < 1964){
           return "Baby Boomers";
      }
      if( year < 1980 ){
           return "Generation X";
      }
      if( year < 1996){
           return "Millenials";
      }
      // Etc...
}
Borjab
  • 1,339
  • 7
  • 16
  • 3
    While this would suffice for Java, this question is tagged C#. – Greg Burghardt Feb 02 '20 at 22:45
  • 1
    @GregBurghardt Just added a link to how to do it in C#. My understanding is that softwareengineering is more about the development practices than the code implementation. This way this question may be code-agnostic – Borjab Feb 03 '20 at 09:05
  • Definitely don't hardcode 2020. And this program will not be helpful for genealogists. – user253751 Feb 03 '20 at 10:46
1

It doesn't have to be complicated:

public interface ISomeInterface
{
    // Returns the amount by which the desired value *exceeds* 5.
    uint SomeMethodLess5(string a);
}
Matt Timmermans
  • 537
  • 2
  • 6
1

You can write a unit test to find all implementations of an interface, and run a test against each of them.

var iType= typeof(ISomeInterface);
var types = AppDomain.CurrentDomain.GetAssemblies()
    .SelectMany(s => s.GetTypes())
    .Where(p => iType.IsAssignableFrom(p));

foreach(var t in types)
{
    var result = t.SomeMethod("four");
    Assert.IsTrue(result > 4, "SomeMethod implementation result was less than 5");
}

When a new implementation of ISomeInterface is added to your project, this test should be able to test it and fail if it returns something less than 5. This, of course, assumes you can test it properly from the input to this method alone, which I am using "four" here. You might need to do other ways to setup this call.

Neil N
  • 602
  • 3
  • 11
  • Problem I can imagine with this is that a valid input isn't defined. How will you know that "four" is an acceptable input for that interface implementation? – Tschallacka Feb 05 '20 at 12:33
  • @Tschallacka I don't. like I said, "You might need to do other ways to setup this call" – Neil N Feb 05 '20 at 13:41
0

Intellisense Code Documentation

Most languages have a form of Intellisense Code Documentation. For C#, you can find information on it here. In a nutshell, it is comment documentation the you IDE Intellisense can parse, and make available to the user when they want to use it.

What your interface documentation says is the behavior of a call is the only real contract of how it should behave. After all, your interface doesn't know how to tell the difference between a random number generator that gives good output, and one that always returns 4 (determined by a perfectly random die roll).

Unit Tests to verify the documentation is implemented faithfully

After the documentation, you should have a unit test suite for your interface that given an instance of a class that implements the interface, gives off the expected behavior when run through various use cases. While you could bake the unit tests into an abstract class to reinforce the behavior, that is overkill and will probably cause more pain than it's worth.

Meta Annotations

I fill I should also mention some languages also support some form of meta annotations. Effectively assertions that are evaluated at compile time. While they are limited in the types of checks they can do, they can at least verify simple programing mistakes at compile time. This should be considered more a compiler assist with the Code Documentation than an enforcer of the interface.

Tezra
  • 238
  • 1
  • 10
-1

In Java if you have a custom constructor, the empty constructor doesn't apply anymore and so you can force calling the custom constructor. My understanding is that C# works the same.

So you could have something like

public class SecondClass {
     public SecondClass(//some argument){
     // do your checks here and throw an exception
     }
}

public abstract class FirstClass extends SecondClass{
    public FirstClass(){
     // we're forced to call super() here because no empty constructor in SecondClass
     super(//some argument)
      }
}

This is pretty rough, but you get the idea.

stackzebra
  • 101
  • 2