Analyzing RavenDB 4.0 with NDepends
I have a… difficult relationship with NDepends, on the one hand, it is an invaluable tool to provide you with good insight into your project. On the other hand, I don’t always agree with its recommendation and I have to teach it what I consider valuable. In my mind, it is the kind of tool that you reach for when you need the Expert Mode.
We use it for exploring our API surface area and seeing that it makes sense, to validate breaking changes and in general to get detailed view into troublesome spots in the codebase.
For example, it pointed out this guy as having to many parameters. I can’t say that I disagree, and it is a prime candidate to refactor to make it simpler.
On the other hand, it didn’t get to be this way by accident, it was added to slowly over time. Each new feature or bug fix needed just a bit more, and this grew and grew. At this point, however, this is extremely stable code that rarely changes, and modifying it will take a lot of work.
Just having tools around doesn’t mean that you get to turn off your head .
One important thing is that if you are considering NDepends, you probably want to start doing so very early in the project lifecycle. One of the things that I find most interesting in the new version is the notion of estimated debt. Here is what this looks like for RaveDB 4.0
And here is the value for RavenDB 3.5
This is composed from estimated amount of effort that you need to put to fix things.
The really interesting part is that it does a pretty good job of finding troublesome locations even when you don’t have history / coverage information. For example, here is the list from RavenDB 3.5:
I find it highly amusing to see this. Mostly because the client side implementation is more complex (and are more frequently modified).
Digging a little bit deeper give us this:
And this is where I start arguing with the tool. Or, to be rather more exact, I have more information than NDepends on this usage. AsyncServerClient is how the entire client talks to the server, so it isn’t cohesive and it is certainly too big, but it is pretty much by design.
The details about boxing / unboxing, however, are much more interesting, and it is where you can start doing a lot of interesting things. In particular, you can customize NDepends to give you a lot of details and enforce rules about your codebase.
For example, given our recent need, here is all the big structures in our code:
You can do that for exploring, or you can add that as a rule (warnif).
I don't think good code is about things that tools can measure.
I found none of the examples you mentioned worth changing. It's a difficult business domain. That can't be refactored away. I don't see a lot of artificial complexity here which is the only thing that can be refactored away.
There's nothing intrinsically wrong with types with 100 members for example. You can't deduce anything from that number. The AsyncServerClient is a great example for such a type. It is cohesive: Wanna call the server? Use this type. I find that simple to understand.
Tobi, This is one of those things that you want to customize for your own needs, and when you do, it is very valuable. When it isn't a good fit for you, it can be annoying. At the beginning of a project is the best place to start using it, IMO, although I know that it is often use to manage projects whose complexity grown too large. A lot of this advice is generic best practices, and you need to use your insight about it.