3

In company where I work we have lots of "utility" classes, each has lots of code inside (thousands of lines), and they are all static. And one static methods call anothers. The problem here is that when you want to test other classes you have lots of pain when tested function uses these static utility classes inside because you can not override them(as they are static). We are using mockito for testing. Powermock is not an option because it breaks bytecode sometimes and it's much slower than mockito.

What we decided - is to move all them to singletons, step by step, so when testing we can mock singleton instance and test method we want (or even test some utility method because they also need to be covered by autotests). It's simple to do that for small classes, but in our case there are 6006 lines class, 1261 lines class, 2231 lines class and so on. Converting them all into singletons manually is a lot of pain, so is there any way to do that automatically? Or maybe some helpers at least.

Example of untestable code:

static Object methodA()
{
      *very heavy code here*
}

static void methodB()
{
      *some code*
      xx = methodA();
      *here is code I've changed and want to test*
      *some code*
}

Let's imagine I changed something in methodB and want to test my change. So I don't want this methodA to be called as it's not related to the test. But I can't change what methodA does with mockito in such case. However it is possible if inside methodA there is a call to the same singleton method that can be mocked because it is not static.

So if you convert it to something like this

public class A {
    private A(){}

    public Object methodAInner()
    {
        *heavy code here*
    }
    public void methodBInner()
    {
        *some code*
        xx = methodA();
        *here is code I've changed and want to test*
            *some code*
    }


    private static A getInstance()
    {
        return SpringUtils.getBean(A.class); // here can be anything that supports testing
    }

    static Object methodA()
    {
        return getInstance().methodAInner();
    }

    static void methodB()
    {
        getInstance().methodBInner();
    }
}

You can make these SpringUtils return mocked object with real methodB and make almost no changes to existing code(useful for git blame), just remove static modifier and chnge methods names. An what is the most important for legacy - it allows not to change code that uses this utility class.

maxpovver
  • 131
  • 1
  • 5
  • 13
    Moving something into a singleton, because you cannot unit test it properly right now is... melting my mind. – nvoigt Aug 20 '15 at 10:41
  • added example @nvoigt – maxpovver Aug 20 '15 at 10:55
  • How would your automatic tool recognize which methods are fine as static methods and which should be turned into instance methods? – CodesInChaos Aug 20 '15 at 11:38
  • 1
    I'm not sure it's a good idea. Have you considered converting them to instance methods and injecting dependencies in the constructor? – lortabac Aug 20 '15 at 12:18
  • I wish could, but it's legacy code :( If you do that way, you'd have to rewrite all classes that use class(millions lines of code in project), so it is impossible... @lortabac – maxpovver Aug 20 '15 at 12:25
  • 2
    See also: [I've inherited 200K lines of spaghetti code — what now?](http://programmers.stackexchange.com/q/155488/6605) – Arseni Mourzenko Aug 20 '15 at 12:58
  • _6006 lines class_...maybe try selling your code to the big IDE vendors as a stress test for their refactoring algorithms. On a more serious note, having so many lines of code sure is a problem, but as you goal is o have less lines, I'm not sure if there's a way around looking at them all unless there are easily spotted patterns. – null Aug 20 '15 at 13:12

2 Answers2

6

I'm all for fighting fire with fire, but turning bad code (lots of static methods) into other bad code (lots of singletons) doesn't sound like such a good idea to spend time on really.

From what you have written, this is all in a legacy code context, but you do have change access to the source where those static methods reside (maybe all, but the rest is too large to touch upon). So let's take a look at where you want to go ultimately:

  • Why do you want to do that work at all? because the current code is untestable and makes new code hard to test as well
  • Why is it untestable? because of all the static methods
  • Why do we have those static methods? because they are used everywhere, have recursive calls (rely on each other), and hence are hard to get rid off
  • Why do they all rely on each other? because utility classes have a tendency to do a lot of things instead of a single responsibility

My next question and suggestion to you would then be: Why don't you extract one responsibility after the other?

You may have a tough time identifying a responsibility, but you're probably having a concrete use case already that made you aware of this problem in the first place, so that should be a good start.

Once you have a separate, single responsibility and testable class available, you can still use it within the old class to replace parts of that class. Maybe you still have some duplication, because you needed that other static method's code in your new class, but it's still called from some other? Then it's a pointer to your next work area - identify what the common thing is that the two places share and extract it out from both.

Due to the sheer size you portray, this is going to be a slow and long running process, so every small responsibility that's out of that class has to be considered a win. Let the old static method rely on the new class, deprecate it, eradicate it whenever you see a call site. These things take time until you get to a point, where you can finally make that last investment and hole your team up for a day or two to get rid of all the remaining old and problematic code. As long as you take care, that no one uses these old methods for any new or modified code, you're getting closer to that goal. I wish you good luck for the journey.

Addendum - based on the additional information that these utility classes differ between different branches and different teams work with each of these: In short, you are trying to shoot a running target. Given the size of your codebase, the task itself is hard enough for a stable target, but I cannot recommend you continue down that path, until you first stopped that movement. Talk with the other teams and get them on board. If they object in any way shape or form, an already hard task becomes pretty much impossible. Hopefully, they all share your pain (or at least not caring about that part of the codebase) and you can get back to a unified state of these classes by merging them all.

Yes, I do suggest to merge all these branches w.r.t. the utility classes. I know it is its own hell. You have but three choices: make it a common problem (and share the solution work!), completely sever yourself, or just let it be as is.

The severing means that your team on your branch flat out defines itself as a different product line. You stop merging with the other guys and roll your own instead. It may not work, and even if it does, I have seen this come back to hit everyone in the company a few years down the road.

Anyways, every problem that cannot be solved locally within your own team('s codebase) is unfortunately immediately a matter of company politics. It's simple to pick a fight with your teammates to get rid of a nasty class in your team's code, but it's worth a second thought on whether you really want to go to war involving the other teams/stakeholders/managers/etc.

Frank
  • 14,407
  • 3
  • 41
  • 66
  • You got me right, but the problem here is merging hell(even if I leave it's usage for others classes the same). Other developers are working with static version in their branches, so to divide this "utility" clas into subclasses we first have to merge all their branches, stop coding at all and do that refactoring(it will be thousands of lines in hundreds of files) and for all that time no one would able to develop anything because of merging hell. So total rewriting is absolutely not an option. – maxpovver Aug 20 '15 at 13:35
  • Ah no no.. I didn't suggest to subclass or even change the interface of the utilities classes. Clients (other devs) do not see any change to the existing API of course. You will simply redirect the old static methods to the new implementation classes. Unless you're trying to tell me that there exist various branches of these utility classes in various states (i.e. with different methods in each branch) ? – Frank Aug 20 '15 at 13:58
  • "there exist various branches of these utility classes in various states (i.e. with different methods in each branch) " yes :( – maxpovver Aug 20 '15 at 14:16
2

I find it extremely rare that one would need to mock the methods of a utility class. A very important OOP principle is that state is represented by objects, and that dictates that static methods - which don't operate on an object - should be stateless.

You don't need to mock functionality. If you are testing code that depends on your factorial implementation, you don't need to mock the factorial function. Not because it has simple implementation, but because you pay very much and gain very little from mocking it. The only thing you gain is the ability to test that code even when factorial is broken, which hardly worth all the mess you need to introduce to the code and all the programming principles you need to break to make this mocking possible. If you need to make sure that factorial(6) returns 720, instead of(syntax may not be accurate):

Factorial factorial = Mockito.mock(Factorial.class);
test.when(factorial.factorial(6)).thenReturn(720);

Won't it be better to add to Factorial's unittests:

Assert.assertEquals(Factorial.factorial(6), 720);

Now you can be sure that factorial(6) will return 720 and you don't need to mock it.

So, you don't mock functionality. What you do want to mock is resources. You mock a web service so the test won't do actual calls to the web service. You mock a database so the tests won't modify the actual data in your database. Rather than making your tests more complex(like mocking the factorial does), mocking these things make the tests simpler, because it relives you of the need to deploy a web service or a database from inside the unit test. They worth the trouble.

So, you only need to mock resources - and resources translate naturally to objects! You don't call a function in a web service using a static method - you have an object that represents the web service(and holds data like it's URL). By the very nature of resources you should be passing them around even if you don't intend to write unit tests, which means you don't need to mess up your code to mock them.

So, if these utility methods don't represent actions on resources - you don't need to mock them. And if they do - they shouldn't have been utility methods in the first place!

Idan Arye
  • 12,032
  • 31
  • 40
  • "And if they do - they shouldn't have been utility methods in the first place!" of course they shouldn't, but I can't do anything with that now... Many years ago they started as utilities, but then became something bigger - for example one class processes login, gui and so on.. And others classes do that too – maxpovver Aug 20 '15 at 13:31
  • ". If you need to make sure that factorial(6) returns 720, instead of(syntax may not be accurate):" This is not what moking is made for. And it should never be used like this. – maxpovver Aug 20 '15 at 13:39
  • Example: `class A {boolean ok() {..} void x(){ if (ok()){ y(); } } } `If we want to check that when ok() return true y() is called, we don't have to rely on ok, we mock it: `A spied = spy(new A()); doReturn(true).when(spied).ok(); spied.x(); verify(spied, times(1)).y();`. This is how it works. – maxpovver Aug 20 '15 at 13:43
  • Of course you shouldn't do mocking like the `factorial` example I gave - I gave it as an anti-example of what not to do. My argument that all utility methods are like this, and a utility method that isn't like this shouldn't have been a utility method in the first place. If you need to verify that `y()` is called inside the utility method then the utility method depends on global state. If you turn it into a singleton you still depend on global state(the singleton), but now you also need to pass that global state around as parameters, and sometimes you'll pass it and sometimes you won't... – Idan Arye Aug 20 '15 at 14:32
  • 1
    "and a utility method that isn't like this shouldn't have been a utility method in the first place" If I'm reading the OP correctly, maxpovver agrees with this, but given their lack of a time machine is now in the position of having to deal with the fact that it *was* treated as a utility method. – Ben Aaronson Aug 20 '15 at 14:34
  • I fail to see how these method could have "started as utilities, but then became something bigger". Did a function start as a factorial, and then someone added to it some code so it'll initialize the database as a side effect? If a function starts as a utility, it stays a utility, and if someone adds resource-dependencies to it I'd say they are trying to sabotage the project on purpose. And about "one class processes login, gui and so on.. And others classes do that too" - that's code duplication, not shifting methods from utility to not utility. – Idan Arye Aug 20 '15 at 14:44
  • @IdanArye when that class was written(15 years ago) there was no DI, and devepoers decided that making it static is the best way to store the same state for class's users – maxpovver Aug 20 '15 at 15:21
  • If they were using static global state they weren't utility classes in the first place - they were singletons in disguise - so you are not making things worse by removing the disguise and converting them to "proper" singletons. However, since you want DI, you'll have to change the interface, which means you'll have to change the code that calls them(or you won't be able to mock them when testing that code) - so why not do the right thing and make them proper objects? – Idan Arye Aug 20 '15 at 15:37
  • 1
    @maxpovver "when that class was written(15 years ago)... " and this is Java we are talking about? Year 2000 called, they want their ***Java 1.3*** back! Is it really going to be so painful to just press for a complete re-write, instead of trying to modernize teenager-age code? Just like teenagers IRL, they may be hard to manage at times... – h.j.k. Aug 20 '15 at 16:28