1

So this may come off a bit broad and generalized, but after spending some time working around under developers, I've come to notice many different methods and design patterns. However, one big thing that sticks out to me is how mind-bendingly complex some of these relatively simple (logic-wise) classes can get.

For example, I recently built a service that synchronizes data between SalesForce and Zendesk. Now there are a lot of different business rules that have to be taken into considerable, but I built the application around the MVVM code pattern.

But once you dig in a bit deeper, you'll notice a lot of complexity and you start digging through a lot of classes to figure out quite what's going on.

For example, here's a class diagram of my synchronization tool.

Class Diagram

Even a casual glance will be able to show some of the complexity, and it gets even more difficult when you dig into the actual code behind the individual methods.

Not only are you interfacing between two separate APIs, you're also interacting with multiple protocols (SOAP and REST). Both of these services are prone to hiccups and failures, so fallbacks are necessary to automatically retry upon failure. Then you need to map all the fields correctly back and forth. From there you have to ensure that nothing else manages to fail either. Every single field is a possibility for failure, and each feature you add makes in exponentially complex.

How can you mitigate some of this complexity?

  • 3
    There are entire books written about this topic. I think an answer here wouldn't do the subject justice. – Lawrence Aiello Nov 18 '15 at 20:42
  • Oh I definitely understand. When getting my degree, I read through dozens of books that covered hundreds of patterns, and yet in the real world, many of these solutions don't quite seem to do justice for certain projects. This project in particular stuck out to me as one. – JD Davis Nov 18 '15 at 20:43
  • I agree that this is too broad. We could potentially answer this question for a specific class or function if shown the code for it, but not in the general case. In my opinion, the ability and the need to answer questions like this is precisely the part of software development that's more art than science. – Ixrec Nov 18 '15 at 20:44
  • 1
    While @LawrenceAiello's comment holds, I'd say that limiting the responsibility and limiting the amount of state of each class should help somehow. It's often easier to understand a bunch of small orthogonal pieces, as opposed to fewer pieces with complex, interlocked behavior. – 9000 Nov 18 '15 at 20:45
  • 1
    Many applications have a lot of inherent complexity. The best coding practices in the world can make them maintainable, but they won't ever make them simple. Whether your application is one of them I have no idea. – Ixrec Nov 18 '15 at 20:50
2

But once you dig in a bit deeper, you'll notice a lot of complexity and you start digging through a lot of classes to figure out quite what's going on.

In my point of view you have the opposite problem. You have a feeling of complexity because there are too few classes.

Complexity does not come from retry pattern, complexity is price you pay to do not use one essential OOP tool: inheritance.

Without some code it's hard to highlight many defects (you may consider to post relevant parts on Code Review) but few things you should consider:

  • Naming convention if not uniform: different casing (for example camel case for some private methods), names like SfAttach and SfAttach2. Lower case name for properties and underscores here and there. Stick strictly to environment conventions/best practices. Also consider to drop prefixes (Sf, Zd, E) whenever possible.
  • You have many God classes and many helper classes. They usually mean there is something wrong in your design. As it is now you just splitted a big bunch of code in four classes with some pure entities around them. Also consider to use collection classes. Do you have a list of Attachment? You probably need an AttachmentCollection class. Don't span implementation details everywhere in your code.
  • Follow common patterns (introduce them when you don't use them) and implement them correctly: for example I see IDisposable implemented without a protected/private Dispose(bool) method.
  • I'd also guess methods are long and complex (I see few private methods but I think there is a lot to do then methods length must be high). Split your code! I want to understand what a method does from its name and how it does it reading 5/10 lines of code, no more.

What to do?

  • First of all you should describe everything at very high level. It doesn't matter you're using SalesForce and Zendesk. Do it writing your tests and think you have to keep in sync two different services. No names, no implementation details.
  • Create high level abstract classes with all the logic you need. This is your domain, populate it with high level entities. Each time you feel you need an helper class then you're doing something wrong, go back.
  • Implement mocks to complete your tests. You will know your logic is right, regardless concrete implementation. With mocks you can simulate network errors...
  • ...it's time to implement a good retry pattern. If you did everything right you just need to add this complexity to one single ReadField() method and one WriteField() method (or their equivalents if you're working at record level).
  • Now you can derive implementation specific classes. Zendesk and SalesForce. Fill the gap between abstract representation and actual implementation.
  • Add tests specific for services you're using. You may also mock them one by one to simulate errors and weird conditions.
  • You're done.

You will see you have many more classes (probably with less methods) but to understand what's going on you just need to read few lines of code...

1

You create classes (or abstractions) as necessary which each handle one of the complicated bits. For example, you may have a class that will try its best to send a message from your application to a certain server and get a reply, and will either succeed or fail and tell you which one, and will handle possible errors to such an extent that if it fails, the caller just accepts the failure and doesn't try anything else.

That's hard to implement, but it's hard to implement not because you use some wrong strategy or pattern, but because it's just hard. But with this interface, the class is easy to use: Send a message, on success process the reply, on failure process the failure.

And you repeat that, writing multiple classes that each are just as complicated as they ought to be and not any more, and that are each easy to use.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.