Hacker News new | ask | show | jobs
by sievebrain 3725 days ago
The reason Google's Java codebase is impossible to understand is not the language, it's mindless application of 'best practices' like dependency injection on an industrial scale. Guice is open source, anyone can go look at it. Just imagine a big complex server in which the "new" keyword wasn't used anywhere because everything was handled by the dependency injector. You get these things:

1. Things that should be compile time errors turning into runtime errors.

2. An IDE that can't navigate anywhere or usefully analyze the code because everything has interfaces between it.

3. Things being mocked out in unit tests just because they can be, meaning lots of superfluous code and tests that pass when they should fail.

There's nothing about Java that mandates this style of programming. It's the result of years of programmers trying to be clever and finding ways to do things better without being sufficiently cynical about new trends and fashions. Given that Java is 20 years old, there has been plenty of time for people to find ways of making simple things complicated.

Go will suffer this phenomenon too because it's not to do with the languages themselves, it's to do with programmers who have safe corporate jobs finding ways to make themselves stand out from the pack by inventing and spreading 'best practices'. If anything, Go will suffer it worse, because the language itself is so limited, so there's more potential for bizarre hacks disguised as cleverness. Like that Go profiler that worked by rewriting source code to insert stuff between every single line of code.

3 comments

You are predicting the future (of Go) based on the past (of Java).

Non-idiomatic Go (or as you call it "bizarre hacks disguised as cleverness") is frowned upon. One example that comes to mind is the Martini web framework[1], which, once pointed out, resulted in a rewrite[2].

[1] https://github.com/go-martini/martini [2] https://codegangsta.io/blog/2014/05/19/my-thoughts-on-martin...

Non-idiomatic code is frowned on by all programmers working in every language.

The problems start when the "ma" is dropped from idiomatic. There's nothing in the design of any language that can stop people coming up with idioms and ideas that sound clever and become widely adopted but which actually cause problems down the line. Go is not immune to this. I'd argue Go is largely composed of such things to start with!

Very recently I have begun to work as programmer, and having to work with PHP I looked at phpunit and unit testing (first time I do it semi-seriously). The guidelines for mocking that I have found are about dependency injection (phpunit has some helpers, but I think that's it).

What is the alternative to it?

Python has an alternative: to use a technique called "patching". Basically, in Python, every module is just an object you can access, so in your unit-test, you can easily go into that module, replace a symbol inside a module (or a class) for something else, run unit-test, put thing back. Very nice feature which gives you incredible power to not have to code "with tests in mind".
The problem isn't with the idea of using mocks in testing. The problems start, as often the case, when people start religiously applying a design pattern and end up over using or abusing it.

The problematic thought process goes like this:

1. Gee, my tests are really slow and flaky and hard to run because they talk directly to a database. I know! I'll swap out my real database connection with an in memory database.

2. Things are better, but they're still slower than I'd like. Maybe I can just write a wrapper around my database code, make it implement an interface, and then provide a custom implementation just for my unit tests. Instead of having my class construct a database connection directly, I'll make it passed in as a parameter. Then they'll be nice and fast!

3. Whoa, now my tests run instantly! How great. This mocking pattern is awesome. I want to mock everything! But hmm, I have a lot of objects and they often depend on other objects, like services of some sort. And I was told in school that God Objects and global variables are bad. So I can't use those because I want to be a good programmer and good programmers don't use those. This is making my code kind of a mess because now every time I want to construct an object it takes lots of parameters when before it didn't.

4. <reads some article on hacker news> Hm, this dependency injection library sounds like what I want. Instead of constructing my objects directly, I ask an "injector" to do it for me. And it will then consult a table of bindings, and then find or construct the dependencies, and then build the object I want, recursively. All I have to do is [insert long complicated process here].

5. Meanwhile, some other programmers look at what's going on. Interesting! They say. Those programmers know what they're doing and they're converting their whole codebase to use dependency injection. They claim it's a best practice to make testable code. We want our code to be testable too, we should do the same thing. Plus, we can call this refactoring and code maintenance and get paid for doing essentially brainless work, instead of having to take risks on developing new features or fixing bugs.

The story ends like this: pretty quickly this newfound "best practice" is spreading throughout your codebase like a fire, and now when you deploy your app, you get an error whilst the app is running. You then try to figure out what's causing the error but 25% of the time you try to follow a method call you end up looking at an interface instead of code, and when you try to figure out what implementation of that interface your app called you have to read and decipher tons of binding definitions.

The correct place to stop in the above story was at point two. And the reason is not just code readability. Once you start over-using mock objects, you can start to discover that your mock is implementing what you think that service does, not what it actually does, and you can end up with code that's actually buggier than code written with traditional fat test dependencies would have been. Fail.

Thank you, very detailed explanation.
I will point out that the run time problems of guice are trying to be solved with dagger: http://google.github.io/dagger/