O princípio da responsabilidade única é aplicável às funções?

17

Segundo Robert C. Martin, o SRP afirma que:

Nunca deve haver mais de um motivo para uma classe mudar.

No entanto, em seu livro Código Limpo , capítulo 3: Funções, ele mostra o seguinte bloco de código:

    public Money calculatePay(Employee e) throws InvalidEmployeeType {
        switch (e.type) {
            case COMMISSIONED:
                return calculateCommissionedPay(e);
            case HOURLY:
                return calculateHourlyPay(e);
            case SALARIED:
                return calculateSalariedPay(e);
            default:
                throw new InvalidEmployeeType(e.type);
        }
    }

E então afirma:

Existem vários problemas com esta função. Primeiro, é grande e, quando novos tipos de funcionários são adicionados, ele aumenta. Segundo, claramente faz mais de uma coisa. Terceiro, viola o Princípio da Responsabilidade Única (SRP), porque há mais de um motivo para mudar . [ênfase minha]

Em primeiro lugar, pensei que o SRP fosse definido para classes, mas também é aplicável a funções. Em segundo lugar, como essa função tem mais de um motivo para mudar ? Só vejo isso mudando devido a uma alteração no Employee.

Enrique
fonte
5
Parece um caso de polimorfismo.
precisa saber é
Este é um tópico muito interessante. Existe uma chance de você adicionar a seguinte solução para esse problema? Eu gostaria que alguém colocasse uma função calculaPay em cada classe de funcionário, mas isso seria péssimo porque agora cada classe de funcionário pode ser alterada por: 1. Os cálculos de pagamento. 2. adicionando mais propriedades à classe etc.
Stav Alfi

Respostas:

13

Um detalhe frequentemente esquecido do Princípio da Responsabilidade Única é que os "motivos da mudança" são agrupados por atores de casos de uso (você pode ver uma explicação completa aqui ).

Portanto, no seu exemplo, o calculatePaymétodo precisará ser alterado sempre que novos tipos de funcionários forem necessários. Como um tipo de funcionário pode não ter nada a ver com outro, seria uma violação do princípio se você os mantivesse unidos, pois a mudança afetaria diferentes grupos de usuários (ou atores de casos de uso) no sistema.

Agora, sobre se o princípio se aplica às funções: Mesmo se você tiver uma violação em apenas um método, você ainda está mudando uma classe por mais de um motivo, portanto, ainda assim, é uma violação do SRP.

MichelHenrich
fonte
1
Tentei assistir ao vídeo do youtube vinculado, mas depois de 10 minutos sem mencionar o agrupamento por atores de casos de uso, desisti. Os primeiros 6 minutos estão todos divagando sobre entropia, sem motivo aparente. Se você forneceu um local no vídeo em que ele começa a discutir isso, seria útil.
Michael Shaw
@MichaelShaw Tente assistir a partir das 10:40. O tio Bob menciona que o código "mudará por diferentes razões, por causa de pessoas diferentes". Eu acho que isso pode ser o que MichelHenrich está tentando nos indicar.
Enrique
Terminou de assistir o vídeo inteiro do youtube de 50 minutos, a maioria dos quais não era sobre o que deveria esclarecer. Notei na marca das 16:00 que ele já havia se mudado para esse tópico, e ele nunca voltou a ele. A "explicação" se resume essencialmente a isso: "responsabilidade" no SRP não significa que, significa "razões diferentes para a mudança", o que realmente significa "mudanças a pedido de pessoas diferentes", o que realmente significa "mudanças no solicitação de diferentes papéis que as pessoas desempenham ". A "explicação" não esclarece nada, substitui uma palavra vaga por outra.
Michael Shaw
2
@ MichaelShaw, como na citação do livro, se você precisar introduzir diferentes tipos de funcionários, precisará alterar a classe Employee. Funções diferentes podem ser responsáveis ​​pelo pagamento de diferentes tipos de funcionários; portanto, nesse caso, a classe Employee deve ser alterada para mais de uma função, daí a violação do SRP.
9136 MichelHenrich
1
@ MichaelShaw sim, você está certo - o SRP depende de como a organização está organizada. É exatamente por isso que adiciono "pode" ou "pode" a todos os meus comentários :). No entanto, mesmo nesses casos, embora o código possa não violar o SRP, ele certamente viola o OCP.
9136 MichelHenrich
3

Na página 176, capítulo 12: Emergência, na seção intitulada Classes e métodos mínimos, o livro fornece uma correção, afirmando:

Em um esforço para tornar nossas classes e métodos pequenos, podemos criar muitas classes e métodos minúsculos. Portanto, essa regra sugere que também mantenhamos nossa função e a contagem de classes baixa

e

Contagens elevadas de classe e método são algumas vezes o resultado de um dogmatismo sem sentido.

Obviamente, ele está falando sobre dogmatismo ao seguir o SRP para quebrar perfeitamente pequenos métodos inocentes como calculatePay()acima.

Mike Nakis
fonte
3

Quando Martin aplica o SRP a uma função, ele está implicitamente estendendo sua definição de SRP. Como o SRP é uma formulação específica de OO de um princípio geral, e como é uma boa ideia quando aplicada a funções, não vejo problema com isso (embora possa ter sido bom se ele o incluísse explicitamente no definição).

Também não vejo mais de um motivo para mudar, e não acredito que pensar no SRP em termos de "responsabilidades" ou "razões para mudar" seja útil. Essencialmente, o que o SRP está dizendo é que as entidades de software (funções, classes etc.) devem fazer uma coisa e fazê-lo bem.

Se você der uma olhada na minha definição, ela não é menos vaga do que a redação usual do SRP. O problema com as definições usuais do SRP não é que eles sejam muito vagos, mas que eles tentam ser muito específicos sobre algo que é essencialmente vago.

Se você observar o que calculatePayfaz, está claramente fazendo uma coisa: despacho com base no tipo. Como o Java possui maneiras integradas de fazer o envio baseado em tipo, calculatePayé deselegante e não-idiomático, portanto deve ser reescrito, mas não pelos motivos declarados.

Michael Shaw
fonte
-2

Você está certo @Enrique. Não importa se é uma função ou um método de uma classe, o SRP significa que nesse bloco de código você faz apenas uma coisa.

A declaração 'motivo para mudar' às vezes é um pouco enganadora, mas se você alterar calculateSalariedPayou calculateHourlyPayou o enum de Employee.typeque precisa alterar esse método.

No seu exemplo, a função:

  • verifica o tipo de funcionário
  • chama outra função que calcula o dinheiro de acordo com o tipo

Na minha opinião, não é diretamente uma violação do SRP, pois os casos de comutação e as chamadas não podem ser gravadas mais curtas, se você pensar em Employee e os métodos já existirem. De qualquer forma, é uma violação clara do princípio de aberto-fechado (OCP), pois você deve anexar instruções 'case' se adicionar tipos de funcionários; portanto, é uma implementação ruim: refatorar.

Não sabemos como o 'Dinheiro' deve ser calculado, mas a maneira mais fácil é ter Employeecomo interface e algumas implementações concretas com getMoneymétodos. Nesse caso, toda a função é desnecessária.

Se for mais complicado calculá-lo, pode-se usar o padrão de visitante, que também não é 100% SRP, mas é mais OCP do que um caso de troca.

Aitch
fonte
2
Não sabe ao certo como é possível listar duas coisas que a função faz, mas diga que não é uma violação do SRP.
JeffO 9/03/2015
@ JeffO: Isso não é duas coisas, são duas partes de uma coisa: chamar a função apropriada com base no tipo.
Michael Shaw