Shane A. Stillwell
Three Things You Should Not Do in a Library

Three Things You Should Not Do in a Library

No, not that kind of library, even though doing these in a public library is also frowned upon.

I’m talking about when you create a library for apps to use. Maybe you publish your library to NPM, or make a Go module and publish it to GitHub. You want to make your library easy to work with by application developers. This means, you cannot write library code the same way you would write application code.

Let’s Get Into It

1. Do NOT expect Environment Variables to be set

Environment Variables are great for storing passwords, configurations, and other points of data. These values in environment variables should not be committed to your git repository. In your application, you should use environment variables for your database connection strings, or API tokens to AWS. The drawback to Environment Variables, they are like globals and I don’t have to tell you globals are a bad thing.

Instead:

// Library Code
export function init(config) {
  const conn = postgres(config.dbConnectionString);
}
// Application Code calling library functions
import theLibrary from 'the-library';
const myVars = {
  dbConnectionString: process.env.DB_CONNECTION_STRING,
  awsToken: process.env.AWS_TOKEN,
};

const result = theLibaray.init(myVars);

Even though NODE_ENV is a very common environment variable in NodeJS / JavaScript, it should not be used in library code. The application code should handle the environment.

2. Do NOT log from your library

You heard me, don’t be printing logs. Logging creates a dependency on a logging library, or worse, just assumes console.log in JavaScript is good enough. If you really need to log in the library, then you could accept a function from the application code that you can call to log.

For example

// Library code using logFn
function getFoo(config: any, logFn?: () => void) {
  // ... stuff
  // If a log function is passed in, we use it to log some data
  if (logFn) {
    logFn('ERROR', 'there was an error');
  }
}
// Application code calling library function with log function
function logFn(level: string, str: string) {
  console.log(level, ' ', str);
}

const result = theLibrary.getFoo(myVars, logFn);

3. Do NOT read / write to the file system*

Your library should not expect files to be in certain locations or even expect a path to be passed for you library to read/write. Why? What if your library is being used in AWS Lambda? What if the values in the file are not held in a file, but environment variable? Not even a location or pathname? No.

If you need some content from a file, let the calling application do the reading for you. This will make testing 100% easier and faster as well since you don’t have to have files laying around to test.

import ( "os")
func main() {
  data, err := os.ReadFile("file/name.txt")
  if err != nil {
    println(err.Error())
    os.Exit(1)
  }

  myLibrary.doSomething(data)
}

*The caveat of course if reading/writing to files is what your library specifically does, then … bombs away.

4. BONUS Do NOT ever exit

This is the worst of all. Never called process.exit() (NodeJS) or os.Exit() (Golang). You should be throwing an error in JavaScript or returning an error in Golang. Exiting the process is not for your library to decide. Maybe the application is just checking and has methods to recover or take other actions when a failure happens. If you exit the process in your library, you should be ashamed and have to sit in the corner.

Conclusion

Does it make sense? Committing these infractions in your library makes it much more difficult to test or work with in environments you might not have planned for. This will also make your library more flexible and portable.