5

I have an Android library used by a decent amount of people, let's call it library A.

I am building a second library, library B.

The two libraries are conceptually related, they solve different problems in the same domain. It's likely that the two libraries will be used together, but it's not mandatory.

The problem: library A contains some classes and interfaces I'd like to reuse in library B.

Are there any best practices on how to do this? This is the first time I encounter this problem and, being the libraries used by other people, I'd like to implement the solution that makes more sense.

I can see three approaches:

  1. Copy-paste interfaces and classes I want to reuse, from library A to library B.
  2. Import library A in library B.
  3. Take out of library A the interfaces and classes I want to reuse, publish them under a new CORE library and import the CORE library in both library A and library B.

Are there more possible approaches I am not considering?

Problems I can see with this solutions:

  1. This is the worse solution. It doesn't follow the DRY principle and, most importantly, a user of both libraries would end up having interfaces like: com.libraryA.Interface1 and com.libraryB.Interface1. This doesn't allow for nice interaction between library A and B.
  2. Library B would import a bunch of code that it doesn't need. Most importantly, users of library B would always have to import library A, because of the shared interfaces from A to B.
  3. This seems the most elegant solution, but how should I handle versioning in this situation? Will library A, B and CORE always be required to have the same version number? If not, would it make sense to have different version numbers for A, B and CORE? Changes on CORE would affect both A and B and require a version bump on all three. Changes on A and B would be isolated.

Are there any more problems I am not seeing?

4

Whether your third option is the correct one depends on the situation. The basic question is whether A currently contains two things that really are reasonably separate and each is useful as a distinct entity.

For example, if you break A apart into two pieces, and have a core with both A and B as clients, is it likely that the core would also do a good job of supporting C, D and E?

Or, with A broken into Core and A-prime, would you end up with a single thing that's basically broken down the middle, and most likely a C, D or E would still need to use pieces of both the Core and A-prime?

Once you've determined that, the conclusion probably becomes pretty obvious: if A really does represent two separate things that happen to (currently) live in the same library, and separating them gives a Core that's likely to be more cleanly usable on its own, then go ahead and separate them.

On the other hand, if it just happens that almost any client of A is likely to use 80% (or whatever) of what it provides, but each will use a different 80%, then it makes more sense to leave it as a whole.

As for versioning: whether you leave A as a single piece or break it into two, you have pretty much the same problem: one library with (at least) one other that depends on it.

One fairly common strategy is to break versioning into two categories: minor changes are things like bug-fixes, but for a client that depends only on documented behavior, a newer version of the library should act as a substitute for any older version. This means you can fix bugs and add features, but not make any material changes to documented behavior.

If you make a change to documented behavior, that would be a major version change. It requires that a client be aware of the change.

So, whether you break A in half or not, you have B depending on something else. For minor versions, some client might depend on having 1.1 or newer, and another client might depend on having 1.2 or newer (but in both cases, the "or newer" only includes 1.x, not some possible 2.x).

Having the same version numbers for all of them is mostly an advantage when you have a number of libraries that clearly are separate, but are (for whatever reason) used together sufficiently often that it's easier for a user to keep track of them together, so if s/he's using both A and B, they don't have to keep track of three separate version numbers.

  • "The basic question is whether A currently contains two things that really are reasonably separate and each is useful as a distinct entity." Core would offer no functionalities to the users, it would only be used as a base to build stuff on top of it. That's why can't make up my mind about it, it's separate, but useless by it self. Does it make sense to force users to import the Core library only to be able to use library A? (because of the shared interfaces it contains) – Pierfrancesco Soffritti Jun 17 '18 at 18:50
  • 1
    If the Core can't reasonably be used separately from A, then it shouldn't exist separately from A. – Jerry Coffin Jun 17 '18 at 18:55
  • I've edited the question. Solution 2. has one significant problem I had overlooked. – Pierfrancesco Soffritti Jun 17 '18 at 19:14
6

The obvious and correct solution is (2): make B a client of A.

Sure, B will import more than it needs, but that is the case for 99% of library clients. You're just misled because you know the internals of library A very well, so you are more aware of the inefficiency than normal. The advantages of division of work outweigh this conceptual inefficiency by far.

  • I've edited the question. Solution 2. has one significant problem I had overlooked. – Pierfrancesco Soffritti Jun 17 '18 at 19:14
  • @PierfrancescoSoffritti: ok, you added "Most importantly, users of library B would always have to import library A, because of the shared interfaces from A to B." - but why do you think this is a "significant problem"? – Doc Brown Jun 17 '18 at 20:47
  • Because at that point B would be just an extension library of A, and not usable on its own. – Pierfrancesco Soffritti Jun 18 '18 at 4:57
  • 3
    @PierfrancescoSoffritti: ... and that is a problem because? (And when answering to someone, please address them using the @ symbol, otherwise they won't get a notification in the inbox). – Doc Brown Jun 18 '18 at 5:58
-1

Approach #1 is not necessarily a bad solution, it depends. If A is maintained and evolved by a completely different organization, and you cannot influence the development of A, you might be in a situation where you want to have your own copy of the reused classes to make sure they are stable and you have them under your own control.

Violation of the DRY principle is only a problem if it leads to a situation where you need to maintain the same thing in two places, but that would not be an issue in that situation, because someone else is maintaining A, you will only have to maintain your copy of classes. And the fact there might be two versions of the same interface in different namespaces is no issue as well - because they are in different namespaces.

However, if B should rely on A and updates to newer version of A shall be possible, because you want maintainance and evolvement of your reused classes to be done by the maintainer of A, then go with option 2. In that situation, I don't see a problem with users of library B always having to import library A.

Option 3 is only feasible when you have the development of A and B both under your control.

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.