13 Jul 2023 · Software Engineering

    Careful With Your Imports: 6 Things to Know About Project Dependencies

    9 min read
    Contents

    When we are developing a project, we often need to add different libraries to provide the functionality that is required for our project, for example, a library for helping us to access the database. But before we know it, we might find ourselves using this facility to add project dependencies too often, i.e. adding dependencies consisting of only 11 lines of code to avoid rewriting that code.

    There are lots of benefits to using dependencies but there are also dangers when we are using code from others and this article presents a list of the most important potential pitfalls to be considered before adding a library to your project.

    1. Security issues

    There’s a space of time from when you include a dependency in your project and when a vulnerability is discovered. The Common Vulnerability and Exposures (CVE) database receives notifications about such security issues, but usually when an issue has been solved.

    In different languages, we can find different tools to help us keep track of possible security issues and if the specific dependency version we’re using is listed in the database. But, even using such databases and other sources of information we could find ourselves in a situation like in the following examples:

    • EventStream library. In 2018, it was discovered that a widely-used Node.js module called event-stream had been compromised with malicious code because it was using the flatmap-stream library, which wasn’t maintained and had been taken over by a malicious actor. As a result, any application that had event-stream as a dependency was vulnerable. This led to security breaches and adverse effects.
    • Struts2 vulnerability in the Equifax data breach in 2017 allowed remote code execution and was exploited by hackers to gain unauthorized access to Equifax’s systems and steal sensitive data. It exposed sensitive personal and financial information belonging to over 143 million people.
    • OpenSSL Heartbleed vulnerability in 2014 was a critical security issue that affected a significant portion of the Internet. It allowed attackers to read sensitive information from the memory of affected systems, potentially exposing usernames, passwords, and other confidential data.

    Our strong recommendation is to always keep your project dependencies up-to-date and keep yourself up to date on potential threats by reviewing sites like CVE.

    2. Backwards compatibility and lock files

    One technique that was very controversial when the Go language was new was pinning or locking the versions of the dependencies being used, ensuring that a new version without backward compatibility could not break our application.

    It’s controversial because the Go language was created with the spirit of keeping backward compatibility and, based on that, using the latest version is proper and secure. But what the creators didn’t imagine is that library creators were not doing the same. The developers inevitably broke the interfaces between versions. Keeping your code up-to-date has the trade-off that you need to dedicate time for the adaptation of new versions of libraries and the changes that they may introduce.

    On the other hand, JavaScript and other languages use a lock file to pin the project dependencies’ version and in maintaining the whole dependency tree. But, in a fast-moving environment, each dependency requires a lot of other dependencies and it’s easy to become out-of-date quickly. The dedication and effort required to maintain a project increase when we have to update too many dependencies too often.

    For example, what if we have a dependency A that includes B in version 1 and C in version 1, and suddenly we need to add a new dependency D that requires the B dependency but using version 2? Including D might be impossible if versions 1 and 2 for the B dependency are incompatible.

    Our recommendation is to try to use as few dependencies as possible, keep them updated, and always keep them pinned with lock files.

    3. You Ain’t Gonna Need It (YAGNI) principle

    Most of the time, we are adding a dependency because we need a function. That’s the case that drove the left-pad library to almost break the Internet. Its creator reported the removal of the library (only 11 lines of code) and when it happened all of the other libraries and other projects which had a hard dependency on it started to fail. They could not build their projects anymore and it even affected some popular libraries like React from Facebook.

    This is the content of the library in question:

    module.exports = leftpad;
    function leftpad (str, len, ch) {
      str = String(str);
      var i = -1;
      if (!ch && ch !== 0) ch = ' ';
      len = len - str.length;
      while (++i < len) {
        str = ch + str;
      }
      return str;
    }

    The question you should ask yourself when you go to introduce a dependency is, are you going to need it? really? This is very important because most of the time we have to choose one of the options available for HTTP clients, JSON encoding/decoding, or User Interface (UI), among others, and it’s possible that our choice isn’t optimal. Like in the left-pad example, if the dependency we are using is out-of-maintenance or almost abandoned, we could find ourselves in trouble.

    Our recommendation here is to keep an eye on how many people are behind a given dependency, how often it is updated, how many issues are still open and not resolved on their website, and who uses it.

    The previous issue with the left-pad dependency appeared because the creator had an issue with the npm (Node Package Manager) system regarding licensing and decided to remove their library. This is something that you should keep in mind when thinking about including a library in your project.

    Google vs Oracle because of Java use in Android. In this lawsuit, Oracle claimed that Google’s use of Java in Android infringed upon Oracle’s copyrights and patents. It was resolved in favor of Google in 2021. The case highlighted the complexities and legal challenges that can arise when using software dependencies.

    Our recommendation here is to check the license of the libraries and dependencies you want or need to use to ensure they let you do what you need to do. This is specially relevant if you are doing this in a professional and commercial setting.

    5. Duplicated functionality

    Something most of the developers aren’t keeping in mind is that when they add a library is if it depends on other libraries. This can lead to having different libraries that potentially perform the same actions. Some time ago, in environments like Erlang or Elixir, it was so easy to see different JSON libraries or HTTP clients (that did basically the same thing) being used because one dependency preferred to use one of the libraries and another included in the same project preferred a different library.

    Even in languages like JavaScript you could have nested and isolated dependencies resulting in the inclusion of the same dependency but in different versions for different parts of our code.

    Our recommendation here is to check your dependency tree and ensure that there are no functionally duplicated dependencies.

    6. Unused or too complex dependencies

    If duplication functionality is bad, unused dependencies are much worse because you have a section of dead code in your project. Dead code does not affect the execution of your code but it increases the size of your code base.

    Dead code in the dependencies is even worse because we need to modify the dependency and sometimes this can be difficult. But even if there’s no dead code, the features of a given dependency may be much more than what we use or what we need.

    For example, when we are using Bootstrap for a landing page it gives us a lot of features we are not going to use and we are not going to need. We could use a tool for shrinking the library and keep only what we are using, but that’s problematic because then we might find ourselves adding another dependency for this purpose. Should we use Bootstrap, then? Of course! We can, but it’s better if we could download a minimal version of the library instead of the fullest one.

    On the other hand, continuing with the Bootstrap example, if we are using Tailwind, we are adding a new dependency to our project that’s giving us a flexible way to create classes for CSS. Tailwind is a smaller dependency than Bootstrap and it includes the capacity to create the CSS we are going to need and no more. But in addition, it gives us trade-offs, e.g. it is harder to implement web user interfaces. If you are a front-end senior developer, you could be more inclined to use Tailwind and take advantage of the benefits. On the other hand, if we are talking about backend developers, then it’s more common to choose Bootstrap. But there’s plenty of options like Bulma CSS that are somewhere in the middle.

    Our recommendation here is to find the ideal dependency for the functionality that you need to cover and if you don’t need it anymore, remove it.

    Conclusion

    As we mentioned at the beginning of the article, there are benefits to using libraries like code reuse,  and implementing faster solutions instead of focusing on non-functional details, among others, and because of these benefits, we need to continue using dependencies. This article is just a heads-up for things that you should keep in mind before including a dependency in your project. You want to make sure that the library you have selected to solve a problem does not end up causing other problems down the road.

    Testing and checking the dependencies is mandatory when using libraries professionally, because using dependencies is always a risk. You can read each of these points as bits of advice and keep them in mind when you need to add a dependency to your project.

    Leave a Reply

    Your email address will not be published. Required fields are marked *

    Avatar
    Writen by:
    Manuel has been a developer since he was 12 years old he started with Basic, like others, but later also with Modula-2, Pascal, C, Assembler, and these before he was 20 years old. In his professional career, he has used Perl, PHP, Python, Ruby, Java, and JavaScript, and since 2009 he was more on Erlang and since 2016 he started with Elixir and Go. He can be considered a polyglot programmer and he loves to teach and mentor others.
    Avatar
    Reviewed by:
    I picked up most of my soft/hardware troubleshooting skills in the US Army. A decade of Java development drove me to operations, scaling infrastructure to cope with the thundering herd. Engineering coach and CTO of Teleclinic.