Methods should not perform too many tasks (Brain Method).

## Why is this an issue?

This issue is raised when Sonar considers that a method is a 'Brain Method'.
A Brain Method is a method that tends to centralize its owner’s class logic and generally performs too many operations. This can include checking too many conditions, using lots of variables, and ultimately making it difficult to understand, maintain and reuse.
It is characterized by high LOC number, high cyclomatic and cognitive complexity, and a large number of variables being used.

### What is the potential impact?

Brain Methods are often hard to cover with tests, because of their deep nesting, and they are error-prone, because of the many local variables they usually introduce. Such methods will be very hard to read and understand for anyone outside who created them, and therefore hard to maintain and fix if bugs get spotted.
They also enable code duplication since the method itself can hardly be reused anywhere else.

## How to fix it

The common approach is to identify fragments of the method’s code that deal with a specific responsibility and extract them to a new method. This will make each method more readable, easy to understand and maintain, easier to test, and more prone to be reused.
In this paper, the authors describe a systematic procedure to refactor this type of code smell: "Assessing the Refactoring of Brain Methods".

### Code examples

#### Noncompliant code example

    void farmDailyRoutine() {
        Crops southEastCrops = getCrops(1, -1);
        Crops eastCrops = getCrops(1, 0);
        WaterContainer waterContainer = new WaterContainer();
        List<Bottle> bottles = new ArrayList<>();
        for(int i = 0; i < 10; i++) {
            var bottle = new Bottle();
            bottle.addWater(10L);
            bottle.putCap();
            bottle.shake(2);
            bottles.add(bottle);
        }
        waterContainer.store(bottles);

        Truck t1 = new Truck(Truck.Type.TRANSPORT);
        t1.load(waterContainer);
        if(Weather.current != Weather.RAINY) {
            WaterContainer extraWaterContainer = new WaterContainer();
            List<Bottle> extraBottles = new ArrayList<>();
            if(southEastCrops.isDry()) {
                for(LandSlot ls : southEastCrops.lands()) {
                    Bottle b = new Bottle();
                    b.addWater(10L);
                    b.putCap();
                    extraBottles.add(b);
                }
            } else {
                extraBottles.add(new Bottle());
            }
            if(eastCrops.isDry()) {
                for(LandSlot ls : southEastCrops.lands()) {
                    Bottle b = new Bottle();
                    b.addWater(10L);
                    b.putCap();
                    extraBottles.add(b);
                }
            } else {
                extraBottles.add(new Bottle());
            }
            extraWaterContainer.store(extraBottles);
            t1.load(extraWaterContainer);
        } else {
            WaterContainer extraWaterContainer = WaterSource.clone(waterContainer);
            t1.load(extraWaterContainer)
        }
    }

#### Compliant solution

    void farmDailyRoutine() { // Compliant: Simpler method, making use of extracted and distributed logic
        Crops southEastCrops = getCrops(1, -1);
        Crops eastCrops = getCrops(1, 0);
        WaterContainer waterContainer = new WaterContainer();
        List<Bottle> bottles = getWaterBottles(10, 10L, true);
        waterContainer.store(bottles);

        Truck t1 = new Truck(Truck.Type.TRANSPORT);
        t1.load(waterContainer);
        if(Weather.current != Weather.RAINY) {
            WaterContainer extraWaterContainer = new WaterContainer();
            fillContainerForCrops(extraWaterContainer, southEastCrops);
            fillContainerForCrops(extraWaterContainer, eastCrops);
            t1.load(extraWaterContainer);
        } else {
            WaterContainer extraWaterContainer = WaterSource.clone(waterContainer);
            t1.load(extraWaterContainer)
        }
    }

    private fillContainerForCrops(WaterContainer wc, Crops crops) { // Compliant: extracted readable and reusable method
        if(crops.isDry()) {
            wc.store(getWaterBottles(crops.lands().size(), 10L, false));
        } else {
            wc.store(Collections.singleton(new Bottle()));
        }
    }

    private List<Bottle> getWaterBottles(int qt, long liquid, boolean shake){ // Compliant: extracted readable and reusable method
        List<Bottle> bottles = new ArrayList<>();
        for(int i = 0; i < qt; i++) {
            Bottle b = new Bottle();
            b.addWater(liquid);
            b.putCap();
            if(shake) {
                b.shake();
            }
            bottles.add(b);
        }
        return bottles;
    }

### How does this work?

In this case, the method `farmDailyRoutine` was taking care of performing many different tasks, with nested conditions and loops, it was long and had plenty of local variables. By separating its logic into multiple single-responsibility methods, it is reusing parts of its original duplicated code and each of the new methods is now readable and easy to understand. They are now also easier to cover with tests, and many other parts of the owner class could benefit from using these methods.

## Resources

### Articles & blog posts

- "Object-Oriented Metrics in Practice: Using Software Metrics to Characterize, Evaluate, and Improve the Design of Object-Oriented Systems" by M. Lanza, R. Marinescu
- "Assessing the Refactoring of Brain Methods" by S. Vidal, I. Berra, S. Zulliani, C. Marcos, J. A. Diaz Pace