Qual é o significado de 'alta coesão'?

28

Sou um estudante que recentemente ingressou em uma empresa de desenvolvimento de software como estagiário. De volta à universidade, um dos meus professores costumava dizer que precisamos nos esforçar para alcançar "Baixo acoplamento e alta coesão".

Entendo o significado de baixo acoplamento. Significa manter o código de componentes separados separadamente, para que uma mudança em um local não o transforme em outro.

Mas o que se entende por alta coesão. Se isso significa integrar bem as várias partes do mesmo componente, não entendo como isso se torna vantajoso.

O que se entende por alta coesão? Um exemplo pode ser explicado para entender seus benefícios?

Máx.
fonte
1
O artigo da wikipedia não responde suficientemente à sua pergunta? pt.wikipedia.org/wiki/Coesion_(computer_science)
Eoin Carroll
Há um bom artigo sobre isso em: msdn.microsoft.com/en-us/magazine/cc947917.aspx
NoChance
1
@EoinCarroll: Infelizmente, o artigo da Wikipedia no momento atual não fornece bons exemplos concretos com os quais novos programadores possam trabalhar. A teoria é boa e tudo, mas realmente não se mantém até você cometer os erros em torno da baixa coesão. A alta coesão é um daqueles tópicos que levei vários anos de programação para entender completamente por que é importante e como alcançá-lo.
Spoike
Eu realmente nunca entendi Coesão até ler Clean Code. Você deveria também.
Sebastian Redl

Respostas:

25

Uma maneira de observar a coesão em termos de OO é se os métodos da classe estão usando algum dos atributos privados. Usando métricas como LCOM4 (Falta de Métodos Coesivos), conforme apontado pelo gnat nesta resposta aqui , você pode identificar classes que podem ser refatoradas. A razão pela qual você deseja refatorar métodos ou classes para ser mais coesa é que isso torna o design do código mais simples para outras pessoas o usarem . Confie em mim; a maioria dos líderes técnicos e programadores de manutenção o amarão quando você corrigir esses problemas.

Você pode usar ferramentas em seu processo de criação, como o Sonar, para identificar baixa coesão na base de código. Há alguns casos muito comuns em que posso pensar em que os métodos são baixos em "coesão" :

Caso 1: O método não está relacionado à classe

Considere o seguinte exemplo:

public class Food {
   private int _foodValue = 10;

   public void Eat() {
     _foodValue -= 1;
   }

   public void Replenish() {
     _foodValue += 1;
   }

   public void Discharge() {
     Console.WriteLine("Nnngghhh!");
   }
}

Um dos métodos, Discharge()carece de coesão porque não toca em nenhum membro privado da classe. Neste caso há apenas um membro particular: _foodValue. Se ele não faz nada com os internos da turma, então realmente pertence a ele? O método pode ser movido para outra classe que poderia ser nomeada, por exemplo FoodDischarger.

// Non-cohesive function extracted to another class, which can
// be potentially reused in other contexts
public FoodDischarger {
  public void Discharge() {
    Console.WriteLine("Nnngghhh!");
  }
}

Ao fazer isso em Javascript, como as funções são objetos de primeira classe, a descarga pode ser uma função gratuita:

function Food() {
    this._foodValue = 10;
}
Food.prototype.eat = function() {
    this._foodValue -= 1;
};
Food.prototype.replenish = function() {
    this._foodValue += 1;
};

// This
Food.prototype.discharge = function() {
    console.log('Nnngghhh!');
};
// can easily be refactored to:
var discharge = function() {
    console.log('Nnngghhh!');
};
// making it easily reusable without creating a class

Caso 2: Classe de Utilidade

Este é realmente um caso comum que quebra a coesão. Todo mundo adora classes de utilidade, mas elas geralmente indicam falhas de design e, na maioria das vezes, dificulta a manutenção da base de código (devido à alta dependência associada às classes de utilidade). Considere as seguintes classes:

public class Food {
    public int FoodValue { get; set; }
}

public static class FoodHelper {

    public static void EatFood(Food food) {
        food.FoodValue -= 1;
    }

    public static void ReplenishFood(Food food) {
        food.FoodValue += 1;
    }

}

Aqui podemos ver que a classe de utilitário precisa acessar uma propriedade na classe Food. Os métodos na classe de utilitário não têm coesão neste caso, porque precisam de recursos externos para fazer seu trabalho. Nesse caso, não seria melhor ter os métodos na classe em que eles estão trabalhando (como no primeiro caso)?

Caso 2b: objetos ocultos em classes de utilitário

Há outro caso de classes de utilitário em que existem objetos de domínio não realizados. A primeira reação instintiva que um programador tem ao programar a manipulação de strings é escrever uma classe de utilidade para ele. Como o que aqui valida algumas representações comuns de strings:

public static class StringUtils {

  public static bool ValidateZipCode(string zipcode) {
    // validation logic
  }

  public static bool ValidatePhoneNumber(string phoneNumber) {
    // validation logic
  }

}

O que a maioria não percebe aqui é que um código postal, um número de telefone ou qualquer outra representação de cadeia de caracteres pode ser um objeto em si:

public class ZipCode {
    private string _zipCode;
    public bool Validates() {
      // validation logic for _zipCode
    }
}

public class PhoneNumber {
    private string _phoneNumber;
    public bool Validates() {
      // validation logic for _phoneNumber
    }
}

A noção de que você não deve "manipular strings" diretamente é detalhada neste post do blog por @codemonkeyism , mas está intimamente relacionada à coesão porque a maneira como os programadores usam as strings colocando lógica nas classes de utilidade.

Spoike
fonte
Agora, se conseguirmos que nossos ORMs manipulem adequadamente nossas classes personalizadas ZipCode e PhoneNumber: |
Pete
+1 bons exemplos. Para o último ponto, ver também sourcemaking.com/refactoring/primitive-obsession
AlexFoxGill
@Pete Seu ORM o manipulará se você fornecer operadores de conversão de string para o seu tipo definido (ZipCode, PhoneNumber): msdn.microsoft.com/en-us/library/85w54y0a.aspx
Marcus
9

Coesão alta significa manter coisas semelhantes e relacionadas juntas, para acoplar ou fundir partes que compartilham conteúdo, funcionalidade, razão ou objetivo . Em outras palavras, baixa coesão poderia, por exemplo, significar uma função / classe / entidade de código que serve a múltiplos propósitos, em vez de estar " ao ponto ". Uma das idéias importantes é fazer uma coisa e fazê-lo bem . Outros podem incluir o fato óbvio de que você não replica funcionalidades semelhantes em muitos lugares. Isso também melhora a localidade da base de código, certos tipos de coisas são encontrados em um determinado local (arquivo, classe, conjunto de funções, ...) ao invés de serem espalhados.

Como exemplo, considere uma classe que serve a dois ou três propósitos: Carrega / armazena recursos (por exemplo, um arquivo) e depois analisa e exibe o conteúdo. Essa classe tem baixa coesão porque gerencia pelo menos duas tarefas separadas que não estão relacionadas de todo (E / S de arquivo, análise e exibição). Um design de alta coesão poderia usar classes distintas para carregar e armazenar o recurso, analisá-lo e exibi-lo.

Por outro lado, o baixo acoplamento visa manter as coisas distintas separadas - para que elas interajam o mínimo possível, o que reduz a complexidade e simplifica o design.

zxcdw
fonte
7

Isso significa que as partes de um determinado objeto estão intimamente relacionadas à função do objeto. Isso significa que há muito pouco ou nenhum desperdício no objeto em termos de função ou responsabilidade. Por sua vez, isso pode melhorar a compreensão do uso do objeto em questão.

Engenheiro Mundial
fonte
isso também não se aplica a um nível mais alto do que apenas objetos? por exemplo, agrupar objetos / funções relacionados a uma tarefa nos espaços para nome?
stijn