Writing Robust Programs

Rajat Gupta
4 min readMar 8, 2019

I’ve been a Java programmer for about 20 years and been writing both small and large complex programs during this while. A large program can have multiple layers (frameworks, external libraries, helper classes, services, services calling other services, UI, controllers). As time goes, frameworks and styles change. What the leading libraries are in a space change (J2EE, Tomcat, Jetty, NGINX). The technology constraints change (installed software, web, single page apps, mobile, installed apps). Here are some of the things that for me have stood through time and that have really given me comfort.

  1. Establish a logging practice — apart from picking a logging library, establish in which cases you want to log things. What will you log consistently. Do you log exception situations when they arise, when the caller that can handle it gets it, or on the exit point of your stateless code. Do you log calls for performance with the associated data? Getting a logging support lib to log more for SQLExceptions vs. other exceptions. Things will go wrong. I’ve used java.util.logging. For service calls, I log on entry with the inputs enough to know what a user was attempting to do. Otherwise, just log whenever there’a business or system failure condition. Log in both cases. Knowing that whatever happens, there’s a log entry for that — is a tremendous safety net for improving in the future. And then log on successful exit, if you have changed data.
  2. Use of Result<Value, Err> — this is a Rust paradigm. Any method that has likely failure scenarios should return something of this sort. It means that instead of just using a return value that could be null or have some other convention to indicate error conditions, the caller must handle the various conditions or explicitly ignore them. In any case, you are giving the caller a really good reason at the very least to add an if condition. If you are dealing w/ NullPointerException hell, which happens more as your code gets complex and you can’t recall all the expectations your code had when being called, do consider this paradigm.
  3. Exceptions and Error classes — Using exceptions also forces dealing w/ them, but often not where you think the caller should. Often, you’ll get a try/catch block around the entire method when you really would need the caller to deal with a case closer to the call itself. Due to the type hierarchy of exceptions, you don’t pass them around in any meaningful manner. Use standard error classes that can hold what you want from an error. I’ve got something that holds the following: a string description, a code, warning/error, log point (line, method, class — auto-derived by pulling up the thread stack).
  4. Solve the problem in the simplest manner possible — if you’re building a helper or service class to do something, implement only the methods that you need and even only the variables in the interface that you need just then. Anything more and you are considering a future possibility. Don’t bother, since when you get to solving that problem, you’ll have much more context, more urgency, and will end up with a clearer path to solution. Modern IDEs help with refactoring so don’t worry about later needing to change what’s already there.
  5. Leverage helper methods and builders with static imports — code clarity and conciseness is brilliant. When you get to tens of thousands of lines of code with all the error handling robust coding requires, having simple methods that do 1 logical thing makes it faster to scan through code for the logic. An example is ServResp.reply(response, ServResp.SC_NOT_FOUND, JSON().str(“msg”, “Document could not be located, it might have been deleted.”)). This is simpler than one to set the HTTP code, another to set the content type, and finally another couple of construct and send back a JSON reply with more structured information.
  6. No return nulls — Search your code for return null. It’s the easiest fastest thing to return. Even if you’re going to do this, add a Optional<MyRealResult> as the return type and return Optional.empty(). This highly encourages the caller to check if the return object is empty or not. You can’t just use the result. Do this even if you don’t want to have your return be Result<MyRealResult, ErrorCondition> which creates more structured approach to return either business or system errors.
  7. Use TypeScript for your Javascript — for anything that involves more than a few Javascript files, use TypeScript. When you start with your libraries and classes, it’s easy to figure out what you need to pass. Then you add support for passing other parameters or others types in the same parameter (like passing a String for a element id or a HTMLElement or a Jquery containing that element) and you realize you want to add comments. TypeScript is basically that, but instead of comments, you’re adding the type definitions directly. VS Code makes it easy to use and incorporate into your programming workflow.

--

--

Rajat Gupta

QvikList is an open-ended collaboration to bring your company, partners and your customers closer. Join us at https://qviklist.com.