Dirty Hacks Are OK
In practically every project you’ve used a “dirty hack”. setAccessbile(true)
, sun.misc.Unsafe
, changing a final
value with reflection, copy-pasting a class from a library to change just one line of wrong code. Even if you haven’t directly, a library that you are using most certainly contains some of these.
Whenever we do something like that, we are reminded (by stackoverflow answers and colleagues alike) that this is a hack and it’s not desirable. And that’s ok – the first thing we should think about when using such a hack, is whether there isn’t a better way. A more object-oriented way, a more functional way. A way that the language allows for, but might require a bit more effort. But too often there is no such way, or at least not one that isn’t a compromise with other aspects (code readability, reuse, encapsulation, etc.). And especially in cases where 3rd party libraries are being used and “hacked”.
Vendors are also trying to make us avoid them – changing the access to a field via reflection might not work in some environments (some JavaEE cases included), due to a security manager. And one of the most “arcane” hacks – sun.misc.Unsafe
is even going to be deprecated by Oracle.
But since these “hacks” are everywhere, including the Unsafe magic, deprecating or blocking any of them will just make the applications stop working. As you can see in the article linked above, practically every project depends on a sun.misc.Unsafe
. It wouldn’t be an understatement to say that such “dirty hacks” are the reason major frameworks and libraries in the Java ecosystem exist at all – hibernate, spring, guava are among the ones that use them heavily.
So deprecating them is not a good idea, but my point here is different. These hacks get things done. They work. With some caveats and risks, they do the task. If instead you’d need to fork a 3rd party library and support the fork? Or suggest a patch and it doesn’t get accepted for a while, but you deadline is soon, these tricks are actually working solutions. They are not “beautiful”, but they’re OK.
Too often 3rd party libraries don’t offer exactly what you need. Either there’s a bug, or some method doesn’t behave according to your expectations. If using setAccessible in order to change a field or invoke a private method works – it’s the better approach than forking (submit an improvement request, of course). But sometimes you have to change a body method – for these use cases I created my quickfix tool a few years ago. It’s dirty, but does the job, and together with the rest of these hacks, lets you move forward to delivering actual value, rather than wondering “should I use a visitor pattern here or “should we fork this library and support it in our repository and maven repository manager until they accept our pull request and release a new version”, or “should I write this with JNI”, or even “should we do this at all, it’s not possible without a hack”.
I know this is not the best advice I’ve given, and it’s certainly a slippery slope – too much of the “get it done quick and dirty, I don’t care” mentality is surely a disaster. But poison can be a cure in small doses, if applied with full understanding of the issue.
In practically every project you’ve used a “dirty hack”. setAccessbile(true)
, sun.misc.Unsafe
, changing a final
value with reflection, copy-pasting a class from a library to change just one line of wrong code. Even if you haven’t directly, a library that you are using most certainly contains some of these.
Whenever we do something like that, we are reminded (by stackoverflow answers and colleagues alike) that this is a hack and it’s not desirable. And that’s ok – the first thing we should think about when using such a hack, is whether there isn’t a better way. A more object-oriented way, a more functional way. A way that the language allows for, but might require a bit more effort. But too often there is no such way, or at least not one that isn’t a compromise with other aspects (code readability, reuse, encapsulation, etc.). And especially in cases where 3rd party libraries are being used and “hacked”.
Vendors are also trying to make us avoid them – changing the access to a field via reflection might not work in some environments (some JavaEE cases included), due to a security manager. And one of the most “arcane” hacks – sun.misc.Unsafe
is even going to be deprecated by Oracle.
But since these “hacks” are everywhere, including the Unsafe magic, deprecating or blocking any of them will just make the applications stop working. As you can see in the article linked above, practically every project depends on a sun.misc.Unsafe
. It wouldn’t be an understatement to say that such “dirty hacks” are the reason major frameworks and libraries in the Java ecosystem exist at all – hibernate, spring, guava are among the ones that use them heavily.
So deprecating them is not a good idea, but my point here is different. These hacks get things done. They work. With some caveats and risks, they do the task. If instead you’d need to fork a 3rd party library and support the fork? Or suggest a patch and it doesn’t get accepted for a while, but you deadline is soon, these tricks are actually working solutions. They are not “beautiful”, but they’re OK.
Too often 3rd party libraries don’t offer exactly what you need. Either there’s a bug, or some method doesn’t behave according to your expectations. If using setAccessible in order to change a field or invoke a private method works – it’s the better approach than forking (submit an improvement request, of course). But sometimes you have to change a body method – for these use cases I created my quickfix tool a few years ago. It’s dirty, but does the job, and together with the rest of these hacks, lets you move forward to delivering actual value, rather than wondering “should I use a visitor pattern here or “should we fork this library and support it in our repository and maven repository manager until they accept our pull request and release a new version”, or “should I write this with JNI”, or even “should we do this at all, it’s not possible without a hack”.
I know this is not the best advice I’ve given, and it’s certainly a slippery slope – too much of the “get it done quick and dirty, I don’t care” mentality is surely a disaster. But poison can be a cure in small doses, if applied with full understanding of the issue.
> […] it’s the better approach than forking (submit an improvement request, of course).
That might depended on the ecosystem you’re in, but short of that, I can’t see how hit’s better. Let’s say I am using a Ruby gem that I desperately need to tweak. I can monkey patch it in my code, but what I find superior is to fork the repo on GitHub, add a commit there and then change my Gemfile to point to that repo. The benefits are:
* When looking at the gem list, you see I’m using a tweak, as opposed to it being hidden somewhere in the code.
* Updates in the gem won’t accidentally break my hack – I’ll have to explicitly update my fork if i want to do so (most of the time, as trivial as `git rebase origin/new-version`)
* I can reuse this across project without copy/paste. In my experience, organizations tend to have the same hacks over libraries that they reuse across projects.
Bonus points that if your “hack” is good code, you can easily open PR.
Short of your package system not supporting GitHub URL, I don’t see how local changes are preferred to forking 🙂
🙂 Which is the case for java package systems, but then – is GitHub an VCS or a package repository? And isn’t using a VCS as a package repository a hack itself?
I’m supporting mostly legacy java systems. Dirty hacks can (and sometimes do) solve problems of a specific programmer (probably under deadline pressur). They’re good for oneliners, demos, etc onetime lovers. But give birth a plethora a system mystics in production(in general you can’t even figure out what happened, e.g. a sysadmin made a security update, seemingly unrelated).
So, the programmer solves it’s problem of the minute, and packages a bomb for the future, for somebody else, forever, until the dirty hack is located and killed.
> And isn’t using a VCS as a package repository a hack itself?
Isn’t it the other way round? Ant, Maven, Gradle, and whatever funny tool use package repositories providing jar files instead of sources, but why?
– It’s faster, but only for the very first build, so who cares?
– It works also with closed source, but currently I can’t recall any closed source Java project.
– I need additional effort to get the sources.
– It’s less safe, as pointing to https://github.com/google/guava/commit/daa84b68f31898f67a51207e193ee2ddabb088bf gives me a thing nobody can tamper with (short of hacking SHA-1 in addition to hacking github/DNS/network).
So maybe all the package repositories with their cryptic names weren’t a good idea at all. Sure, we won’t change it.
By the way, nice article, I fully agree with your conclusion.
That’s a very interesting point. And we actually had a very similar discussion the other day. I guess the dependency repositories are needed primarily because of transitive dependencies. A git commit does not have a way to define the other git commits it relies on to function. We can probably think of some way to include that as metadata, e.g. at the root of the repo, .gitdepend for example?
There are git submodules (which I haven’t tried) which might or mightn’t help. I’m not claiming that git alone is sufficient, it may need something like .gitdepend. Such a thing could be a part of the repository instead of pom.xml and others.
Transitive dependencies could lead to conflicts in the same way they do now. You could compile the sources against the originally stated version and risk problems like NoSuchMethodError during runtime or go through the hassle of making everything works with a single version. There are good chances someone did it before and made it public, so you may be able to find a compatible set of all libraries you need. This would be probably much easier with git than with maven.
If you use dirty hack on regular basis, then maybe you should reconsider your tools (unless you have a condition and your are allergic to learning).
Eg. if you find the visitor pattern too heavy, then the correct answer is not instanceof (dirty hack) but it is to use scala case classes with pattern matching or to use a code generator like https://github.com/derive4j/derive4j
https://tushirnitin.wordpress.com/2019/09/18/15-awesome-sublime-text-plugins-for-web-development/