1

Edge cases when it comes to separation of concerns

Profile picture
Mid-Level Software Engineer at Taro Community23 days ago

So I know the code quality course says sweat details for edge cases.

However what’s your perspective of a scenario where you as an api wants to handle every case, but it turns into a bunch of one offs in terms of code readability?

One approach is to go and gracefully align on a win win solution for client and server.

This area is gray and I want to do the right thing here.

Is the idea more around the communication with my teammates to resolve these edge cases than having every edge case addressed in every layer?

48
5

Discussion

(5 comments)
  • 2
    Profile picture
    Helpful Tarodactyl
    Taro Community
    23 days ago

    If you’re implementing an API and you’re deciding between a complex API with a simple implementation vs a simple API with a complex implementation, you go for the latter.

    A simple example is the UNIX file system commands. A majority of people will use touch, mkdir, cd, ls and pwd. They’re simple commands but if you know anything about filesystems and OS, there are tons of edge cases and are super complex (what if you print pwd on a renamed/deleted directory? Etc.). OS’ have millions of lines of code, yet we interact with them through only a few commands. This is great design since it abstracts away any nitty gritty details about the OS from us. This means even a beginner doesn’t need to know about OS internals and can still interact with it.

    So in your case, you should probably handle every edge case even though it might get a little complicated. With that being said, maybe talk to your teammates about the edge cases and if they say they’ll handle it, then that’ll save you some work, but if it’s vague about who should handle the edge case, then the API implementer should handle responsibility by default.

    To mitigate the readability tradeoff, think about how you can group edge cases together. If they’re coming from a single source, is there a way to pre-process the input? If they’re coming from the same behavior, is there a common way to handle them? If they’re a mix, is there a way to decouple different edge cases? Sometimes it’s difficult to group edge cases and you’ll have to bite the bullet, but at least your team will be grateful that you’re doing the hard work and not them (also a great way to show off how good your skills are)

    Read more of Philosophy of Software Design by John Ousterhout for more details.

  • 0
    Profile picture
    Tech Lead @ Robinhood, Meta, Course Hero
    21 days ago

    Helpful Tarodactyl really nailed it here: Make things simple for the end-user.

    When you're building an API, the end-user is other engineers. You want to make it so that they can access powerful functionality through a simple interface. They should be able to call your API in 10 lines of code, not 100+.

    This is one of the core concepts behind a great engineer: They abstract away pain. They take something that is incredibly complex and expose something that is simple, elegant, and beautiful. All of the nasty complexity is abstracted away underneath the hood.

    Anyways, let's talk tactics when it comes to API design. I am assuming a classic front-end product calling REST API endpoint use-case here. Here are 3 different levels:

    1. Stuff breaks, server shrugs - You hit one of those edge cases and your endpoint just returns 500 to the client, nothing else. The client has 0 clue what's going on. The front-end engineer just has to return a generic error message that isn't actionable at all for the end user. I have seen this so, so many times, both as an app end-user and an engineer. Ugh.
    2. Stuff breaks, custom error codes - You hit an edge case and return a specific error code corresponding to it. The front-end client needs a giant switch statement to map the codes to the proper error message to display to the end-user. This isn't too bad, but it could be better IMHO.
    3. Stuff breaks, human-readable error in response - Not only do you return a reasonable error code, you return an actionable error message that the front-end client can just show directly to the end-user. For example, let's say you have a sign-up endpoint and it turns out that there is already an account tied to the sign-up email (i.e user forgot they had an account). In the JSON response, the back-end can have a field like "error_message: An account already exists for a@b.com, try logging in instead". This moves more complexity to the API/back-end layer (you'll need to worry about stuff like localization), and in exchange, the API is much easier to use. I'm a big believer in "Dumb client, smart server", so I prefer this design.
    • 0
      Profile picture
      Mid-Level Software Engineer
      Taro Community
      21 days ago

      Half of this api will not be used in 6 months due to a critical impactful migration.

      This means that supporting legacy and v2 would mean there will be throwaway code in 12 months to be safe.

      This pattern and advice you’re suggesting is the way to go because id give a great ux to both clients plus also at some point it turns into a snack task because no one will use them?

    • 0
      Profile picture
      Tech Lead @ Robinhood, Meta, Course Hero
      21 days ago

      Interesting, the throwaway aspect makes things interesting. If you have high-confidence in the migration, then there's nothing wrong with half-assing the code. There are more impactful things to work on than throwaway code.

      The tricky part is the confidence angle. I have seen migration take 2 years instead of 2 months. "Throwaway" code can end up lasting a long time, haha. The painful part is when people start building on top of and extending the throwaway code...

  • 0
    Profile picture
    Tech Lead @ Robinhood, Meta, Course Hero
    21 days ago

    Also, if you have a bunch of edge cases, you can keep the code clean with good modularization. The simplest way is to move processing logic into separate methods. For example, you can have a dedicated method called validateInput() that checks for all the ways the input to an API can be malformatted:

    • That method can return true if the input is valid or false if it's invalid.
    • In the false case, the rest of your API logic can stop and you start constructing the appropriate error response.
    • You could even have that validateInput() method return an InputValidationResult object or something instead that has an "invalidInputReason" field if the input was invalid.

    The cool thing about having clear classes/methods with separation of responsibilities is that this makes your code quite easy to unit test, making the code quality even higher!