Is abstraction of library dependencies on implementation a common practice?

My answer to this question will be no. But my colleagues do not agree.

We restore our product and make many critical decisions that need to be made in the short term.

While doing some of my own work, I noticed that we have some internal C ++ classes for abstracting some of the POSIX APIs (threads, mutexes, semaphores, and rw locks) and other utility classes. Please note that these classes are basic and have not been ported from Linux (portability is a factor in rebuilding.) We also use POCO C ++ libraries.

I brought this to the attention of my colleagues and suggested that we cut back on our internal activities in favor of their POCO equivalents. I want to make full use of the library that we already use. They suggested that we implement our own classes using POCO and, if necessary, additional abstract POCO classes so as not to depend on any particular C ++ library (referring to future unknowns - what if we want to use another lib / framework library, for example QT or boost, what if the one we choose turns out to be bad, or the development becomes inactive, etc.)

They also do not want to reorganize obsolete code, and abstracting parts of POCO with our own classes, we can implement additional functionality (classic OOP.) I can appreciate both of these arguments. However, I argue that if we do the transcoding, we must go big or go home. Now the time has come for refactoring, and it really should not be so bad, especially given the similarities between our classes and those in POCO (threads, etc.). I don’t know what to say on the second question - should I use only extended classes where functionality is needed?

My colleagues also don't want to clutter the POCO namespace everywhere. I maintain that we must choose a library / framework / toolkit and stick to it. Make full use of its capabilities. Is this not a typical practice? The only project I've seen that abstracts the whole structure is Freeswitch (which provides its own interface for APR.)

One of the suggestions is that the API that we provide to each other and potential customers should be free of POCO, but it will be present in the implementation (which makes sense.)

None of us really have experience in such design decisions, and it shows in the current product. When I was young, I have some kind of intuition that brought me here, but also no practical experience. I really want to avoid bad solutions to problems that have already been resolved.

I think my question boils down to the following: when creating a product, we must: a) choose the dominant structure on which most of our code is based, and b) expect the structure to be closely related to the product? Isn't that the point of the frame? (Are frameworks or libraries more suitable for POCO?)

+6
source share
2 answers

First, the API you publish should definitely be free of POCO, boost, qt, or any other type that is not part of the C ++ standard library. This is because the base libraries have their own release cycle, different from the release cycle of your library. If users of your library also use boost, but with a different, incompatible version, they will need to spend time fixing incompatibilities. The only exception to this rule is when you create a library that will be released as part of a broader framework - say, an addition to the POCO toolkit. In this case, the release of your library is tied to the release of all the tools.

Inside, however, you should avoid using your own wrappers, unless the library you are abstracting is a true “commodity library" 1 . The reason for this is that when you hide the external library behind your classes, most of the time you mimic the level of abstraction of the library that you are hiding. The code that uses your wrapper will be programmed at the abstraction level dictated by the external library. When you change the implementation behind your shell for another structure, it is very likely that you either (1) adapt the new structure to match the abstraction level of the old structure, or (2) need to change the way you use your wrapper. Both cases are very suspicious: if you do (1), you probably shouldn't switch first, and if you do (2), then your wrappers will be useless.


1 In “commodity library,” I mean a library that provides the level of abstraction that is commonly found in other libraries that serve a similar purpose.
+4
source

There are two situations where I think it's worth having your own wrappers:

1) You have considered several different implementations of mutexes in different systems / libraries, you have established a common set of requirements that all of them can satisfy, and which are sufficient for your software. Then you define this abstraction and implement it one or more times, knowing what you planned ahead for flexibility. The rest of your code is written to rely only on your abstraction, and not on the random properties of the current implementation (s). I have done this in the past, although not in the code that I can show you.

A classic example of this "least common interface" would be to rename a file system abstraction based on the fact that Windows cannot implement an atomic rename file on top of an existing one. Thus, your code should not rely on atomic name substitution if you can replace your current nix implementation with one that cannot do this in the future. You must restrict the interface from the start.

When done correctly, this kind of interface can greatly facilitate any future porting, whether to a new system or because you want to change the dependencies of a third-party library. However, the entire infrastructure is probably too large to handle this successfully - essentially you are inventing and writing your own framework, which is not a trivial task and perhaps more important than writing your real software.

2) You want to be able to mock / stub / sham / spoof / plagiarize / regardless of the following smart method, mutex in tests and decide that you will find it easier if you have your own shell than if you are trying to mess characters from third-party libraries or embedded.

Please note that the definition of your own functions called wrap_pthread_mutex_init , wrap_pthread_mutex_lock , etc., which exactly mimic the pthread_* functions and take exactly the same parameters, may satisfy (2) but not (1). In general, to complete (2), it probably takes more than just shells; you also usually want to embed dependencies in your code.

Doing extra work under the heading of flexibility without actually providing flexibility is pretty much a waste of time. It can be very difficult or even impossible to implement one environment with a stream in terms of another. If you decide to switch from pthreads to std::thread in C ++ in the future, then using an abstraction that looks exactly the same as the pthreads API under different names, (approximately) no help.

For another possible change you could make, implementing the full pthreads API on Windows is possible, but probably more complex than implementing just what you really need. Therefore, if you port to Windows, all of your abstraction saves you time — it is the time to find and replace all calls in the rest of your software. You still have to (a) connect the full implementation of Posix for Windows or (b) do the work to find out what you really need, and only implement it. Your cover will not help in real work.

+4
source

Source: https://habr.com/ru/post/925145/


All Articles