2

Essentially I've got a bunch of formulas in two giant methods in a class designed to do math transformations and evaluations to multiple inputs. Where the inputs are actually lists of inputs (as there are some sums involved too). Later on I want to optimize this code by utilizing GPU/CPU accelerated matrix multiplications and additions but for now I'm using the basic for-loops.

Lets say hypothetically i'd like to grow to several dozen cases and right now i have less than 10..

Something like:

enum EnumType {
    SUPER_FUNCTION,
    MEGA_FUNCTION,
    ..
}

float doMathStuff(EnumType functionType, List<float> a, List<float> b...) {
    switch(functionType) {
        case SUPER_FUNCTION:
                    if(situationA) {
                        switch(something else) {

                        }
                    } else {
                        switch(something else) {

                        }
                    }
                return stuff;
        case MEGA_FUNCTION:
                for(..) {
                    if(situationA) {
                        switch(something else) {

                        }
                    } else {
                        switch(something else) {

                        }
                    }
                }
                return stuff;
        ...
    }
}

My problem is that to support the functions I'm ending up with SEVERAL hundred lines of code in each of my switch statements which is making it rather cumbersome to go through. I shudder to think about maintaining this once I add more cases.

Any ideas as to how to keep this nightmare-in-the-making in check?

BTW: This is my own personal project and I have total freedom to do any changes.

3
  • Does this answer your question? Approaches to checking multiple conditions?
    – gnat
    Commented Apr 5, 2020 at 20:23
  • 2
    If you cannot recollect and recite all potential values of an enum within a few seconds after the moment you define it (provided that you have properly thought about it up front), chances are you are messing up (you are using the wrong kind of abstraction). Use an interface and classes instead. enum constructs do not really serve "future extensibility" that well. Commented Apr 7, 2020 at 1:40
  • While I'm not sure I agree with your reasoning, I do agree that an interface and classes is ultimately more maintainable.
    – Akumaburn
    Commented Apr 8, 2020 at 20:53

2 Answers 2

2

I had similar design goals on a polynomial calculation library I've written. Wanted to have different kinds of operations, some of which have special optimization possibilities.

Long story short: Object-orientation helps a lot, if it is done right.

Since you didn't provide an actual domain, let's use my example of polynomials. Since it's about polynomials I created the Polynomial of course:

public interface Polynomial {
   Polynomial add(Polynomial other) { ... }

   Polynomial dot(Polynomial other) { ... }

   ...etc...
}

However, there are special cases, for example if the polynomial is modulo something, and you add a bunch of them, you can potentially delay the modulo operation (which is pretty expensive), and/or export the calculation to GPU.

Since adding a bunch of Polynomials is some higher operation, I've added it to the PolynomialRing where these things come from. So that has:

public class PolynomialRing {
   ...
   public Polynomial add(Polynomial[] ps) { ... }
   ...
}

Initially this was a for loop using Polynomial.add(), just like in your example, later I optimized it to delay the modulo and use a construct that the JVM translates to SIMD instructions, which is pretty neat.

So, you'll have to model your math domain properly. There is no generic mechanism for everything. For every thing that you want to have or want to optimize you'll have to find a place.

1
  • Decided to go with this type of design. I don't like cluttering with too many classes but the alternative is a giant class which i like even less.
    – Akumaburn
    Commented Apr 8, 2020 at 20:52
4

At a minimum, you should create functions to encapsulate parts of the behavior (and organize the code)

float doMathStuff(EnumType functionType, List<float> a, List<float> b...) {
  switch(functionType) {
    case SUPER_FUNCTION: return handleSuperFunction(args); break;       
    case MEGA_FUNCTION: return handleMegaFunction(args); break;
  }
}

float handleSuperFunction(someArgs) {
  if(situationA) {
    return handleSuperSituationA(args);
  }
  else {
    return handleSuperSituationB(args);
  }
}

float handleSuperSituationA(args) {
  switch(something else) {
    case foo: return blah;
    ...
  }
}

Once you do this, it should be easier for you and others to read and follow. Now, consider if these are functions of "objects" and could be made polymorphic. Without knowing your domain this is difficult for me to say.

Added

One big advantage is that you can name the various subcases. For example, might be handleImaginaryRoot() and handleRealRoot() or some such. (My analytical math is getting a little stale, YMMV...)

1
  • 1
    While this makes things a little easier to read, it doesn't get around the problem of how bloated the class itself is becoming, for that reason I picked the other solution.
    – Akumaburn
    Commented Apr 8, 2020 at 23:04

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.