Abstração: A guerra entre resolver o problema e uma solução geral [fechada]

33

Como programador, me encontro no dilema em que quero tornar meu programa o mais abstrato e geral possível.

Fazer isso normalmente me permitiria reutilizar meu código e ter uma solução mais geral para um problema que pode (ou não) surgir novamente.

Então essa voz na minha cabeça diz: apenas resolva o problema, manequim, é assim tão fácil! Por que gastar mais tempo do que você precisa?

Todos nós já enfrentamos essa questão em que a Abstração está no seu ombro direito e o Resolva-estúpido, no lado esquerdo.

Qual ouvir e com que frequência? Qual é a sua estratégia para isso? Você deveria abstrair tudo?

Bryan Harrington
fonte
1
"Tão abstrato e geral" é geralmente conhecido como arrogância do programador :) No entanto, devido à lei de Murphy, o único grau de adaptação necessário ao trabalhar na próxima tarefa será aquele que você não forneceu.
Matthieu M.
1
Uma das melhores perguntas de sempre!
explorest
1
Você sempre adivinhar errado, por isso é simples. Quando você expande os casos simples "vezes suficientes", começa a ver um padrão. Até lá, não.

Respostas:

27

Qual ouvir e com que frequência?

Nunca abstraia até que você precise.

Em Java, por exemplo, você deve usar interfaces. Eles são uma abstração.

No Python, você não possui interfaces, possui Duck Typing e não precisa de altos níveis de abstração. Então você simplesmente não.

Qual é a sua estratégia para isso?

Não abstraia até ter escrito três vezes.

Uma vez é - bem - uma vez. Basta resolvê-lo e seguir em frente.

Duas vezes é a indicação de que pode haver um padrão aqui. Ou não pode. Pode ser apenas coincidência.

Três vezes é o início de um padrão. Agora transcende a coincidência. Agora você pode abstrair com sucesso.

Você deveria abstrair tudo?

Não.

De fato, você nunca deve abstrair nada até ter uma prova absoluta de que está fazendo o tipo certo de abstração. Sem a "Regra das Três Repetições", você escreverá coisas que são inutilmente abstraídas de uma maneira que não ajuda.

Fazer isso normalmente me permitiria reutilizar meu código

Essa é uma suposição frequentemente falsa. Abstração pode não ajudar em nada. Isso pode ser feito mal. Portanto, não faça isso até que você precise.

S.Lott
fonte
1
"Nunca abstraia até que você precise." - Suponho que você evite o uso de classes, métodos, loops ou instruções if? Essas coisas são todas abstrações. Se você pensar bem, o código escrito em linguagens de alto nível é muito abstrato, quer você goste ou não. A questão não é se deve abstrair. É quais abstrações usar.
Jason Baker
9
@ Jason Baker: "Presumo que você evite o uso de classes, métodos, loops ou instruções if". Não parece ser esse o motivo da pergunta. Por que fazer a afirmação absurda de que toda a programação é impossível se evitarmos abstrair demais nossos projetos?
S.Lott
1
Lembro-me da velha piada em que um homem pergunta a uma mulher se ela vai dormir com ele por um milhão de dólares. Quando ela diz que sim, ele pergunta se ela vai dormir com ele por cinco dólares. Quando ela diz "Para que tipo de mulher você me leva?" a resposta é "Bem, já estabelecemos que você dormirá com alguém por sexo. Agora estamos apenas discutindo o preço". O que quero dizer é que nunca se trata de uma decisão de abstrair ou não. É sobre quanto abstrair e quais abstrações escolher. Você não pode optar por adiar a abstração, a menos que esteja escrevendo uma montagem pura.
Jason Baker
9
@ Jason Baker: "Você não pode optar por adiar a abstração a menos que esteja escrevendo uma montagem pura" Isso é trivialmente falso. Assembly é uma abstração sobre linguagem de máquina. Que é uma abstração sobre o circuito do hardware. Pare de ler demais a resposta que não estava presente em questão. A pergunta não era sobre "Abstração" como um conceito ou uma ferramenta intelectual. Tratava-se de abstração como uma técnica de design OO - aplicada incorretamente - com muita frequência. Minha resposta não foi que a abstração é impossível. Mas essa "super abstração" é ruim.
S.Lott
1
@ JasonBaker, não é uma razão inédita para dormir com alguém. Talvez você estivesse pensando em "dinheiro"?
19

Ah, YAGNI. O conceito mais abusado de programação.

Há uma diferença entre tornar seu código genérico e fazer um trabalho extra. Você deve gastar tempo extra para tornar seu código flexível e facilmente adaptado para fazer outras coisas? Absolutamente. Você deve gastar tempo implementando funcionalidades desnecessárias? Não. Você deve gastar tempo fazendo seu código funcionar com outro código que ainda não foi implementado? Não.

Como diz o ditado "Faça a coisa mais simples que poderia funcionar". O problema é que as pessoas sempre confundem simples com fácil . Simplicidade dá trabalho. Mas vale a pena o esforço.

Você pode ir ao mar? Claro. Mas minha experiência é que poucas empresas precisam mais de uma atitude "faça o que está fazendo agora" do que já tem. A maioria das empresas precisa de mais pessoas que pensem nas coisas antes do tempo. Caso contrário, eles sempre acabam tendo um problema auto-sustentável, onde ninguém nunca tem tempo para nada, todo mundo está sempre com pressa e ninguém faz nada.

Pense da seguinte maneira: é bem provável que seu código seja usado novamente de alguma forma. Mas você não vai prever corretamente dessa maneira. Portanto, torne seu código limpo, abstrato e com poucas conexões. Mas não tente fazer seu código funcionar com peças específicas de funcionalidade que ainda não existem. Mesmo se você souber que existirá no futuro.

Jason Baker
fonte
Boa distinção entre simplee easy!
precisa
"Mas minha experiência é que poucas empresas" - curiosamente, o inverso pode ser verdadeiro na academia.
Steve Bennett
12

Um silogismo:

  1. A generalidade é cara.

  2. Você está gastando dinheiro de outras pessoas.

  3. Portanto, a despesa de generalidade deve ser justificada para as partes interessadas.

Pergunte a si mesmo se você está realmente resolvendo o problema mais geral, a fim de economizar dinheiro às partes interessadas mais tarde, ou se acha apenas um desafio intelectual fazer uma solução geral desnecessariamente.

Se a generalidade for determinada como desejável, ela deverá ser projetada e testada como qualquer outro recurso . Se você não puder escrever testes que demonstrem como a generalidade implementada resolve um problema exigido pela especificação, não se preocupe em fazê-lo! Um recurso que não atende aos critérios de design e não pode ser testado é um recurso no qual ninguém pode confiar.

E, finalmente, não existe algo como "o mais geral possível". Suponha que você escreva software em C #, apenas por uma questão de argumento. Você fará com que todas as classes implementem uma interface? Toda classe uma classe base abstrata com todo método um método abstrato? Isso é bastante geral, mas não é nem de longe "o mais geral possível". Isso apenas permite que as pessoas alterem a implementação de qualquer método por meio de subclassificação. E se eles quiserem alterar a implementação de um método sem subclassificar? Você pode transformar todo método na verdade uma propriedade do tipo delegado, com um setter para que as pessoas possam mudar cada método para outra coisa. Mas e se alguém quiser adicionar mais métodos? Agora todos os objetos precisam ser expansíveis.

Nesse ponto, você deve abandonar o C # e adotar o JavaScript. Mas você ainda não chegou nem perto do geral; e se alguém quiser alterar como a pesquisa de membros funciona para esse objeto em particular? Talvez você devesse escrever tudo em Python.

Geralmente, aumentar a generalidade significa abandonar a previsibilidade e aumentar enormemente os custos de teste para garantir que a generalidade implementada tenha êxito em atender a uma necessidade real do usuário . Esses custos são justificados por seus benefícios para as partes interessadas que estão pagando por isso? Talvez eles sejam; depende de você discutir com as partes interessadas. Minhas partes interessadas não desejam que eu abandone a digitação estática em busca de um nível de generalidade totalmente desnecessário e extremamente caro, mas talvez o seu.

Eric Lippert
fonte
2
+1 por princípios úteis, -1 por fazer tudo sobre dinheiro.
Mason Wheeler
4
@Mason: Não se trata de dinheiro , é de esforço . Dinheiro é uma medida prática de esforço . Eficiência são benefícios acumulados por esforço produzido; novamente, o lucro em dinheiro é uma medida prática dos benefícios acumulados . É simplesmente mais fácil discutir a abstração do dinheiro do que criar uma maneira de medir esforço, custo e benefício. Você prefere medir esforço, custo e benefício de alguma outra maneira? Sinta-se livre para fazer uma proposta de uma maneira melhor.
Eric Lippert
2
Concordo com o argumento de @Mason Wheeler. Acho que a solução aqui é não envolver as partes interessadas nesse nível de projeto. Obviamente, isso depende muito do setor, mas os clientes geralmente não veem o código. Além disso, todas essas respostas parecem anti-esforço, parecem preguiçosas.
Orbling
2
@Orbling: Sou totalmente contra o esforço desnecessário, caro e dispendioso que tira recursos de projetos importantes e dignos. Se você está sugerindo que isso me deixa "preguiçoso", afirmo que você está usando a palavra "preguiçoso" de uma maneira incomum.
Eric Lippert
1
Na minha experiência, outros projetos não tendem a desaparecer, por isso geralmente vale a pena investir o tempo necessário no projeto atual antes de prosseguir.
Orbling
5

Só para esclarecer, fazer algo generalizado e implementar uma abstração são duas coisas completamente diferentes.

Por exemplo, considere uma função que copia memória.

A função é uma abstração que oculta como os 4 bytes são copiados.

int copy4Bytes (char * pSrc, char * pDest)

Uma generalização faria essa função copiar qualquer número de bytes.

int copyBytes (char * pSrc, char * pDest, int numBytesToCopy)

A abstração se presta a reutilizar, enquanto a generalização apenas torna a abstração útil em mais casos.

Mais especificamente relacionado à sua pergunta, a abstração não é apenas útil da perspectiva de reutilização de código. Muitas vezes, se feito corretamente, tornará seu código mais legível e fácil de manter. Usando o exemplo acima, o que é mais fácil de ler e entender se você está percorrendo o código copyBytes () ou um loop for iterando através de uma matriz que move dados um índice por vez? A abstração pode fornecer uma espécie de documentação própria que, na minha opinião, facilita o trabalho do código.

Como regra geral, se eu puder criar um bom nome de função que descreva exatamente o que pretendo que um pedaço de código faça, escrevo uma função para ele, independentemente de achar ou não que vou usá-lo novamente.

Pemdas
fonte
+1 para fazer a diferença entre abstração e generalização.
Joris Meys
4

Uma boa regra geral para esse tipo de coisa é Zero, Um, Infinito. Ou seja, se você precisar do que está escrevendo mais de uma vez, pode assumir que precisará ainda mais vezes e generalizar. Esta regra implica que você não se incomoda com a abstração na primeira vez que escreve algo.

Outro bom motivo para essa regra é que, na primeira vez em que você escrever o código, você não saberá necessariamente o que abstrair, pois possui apenas um exemplo. A Lei de Murphy implica que, se você escrever um código abstrato pela primeira vez, o segundo exemplo terá diferenças que você não previu.

Larry Coleman
fonte
1
Parece muito com a minha versão da regra de refatoração: greve dois! refatorar!
Frank Shearar
@ Frank: Provavelmente é a sua regra de refatoração, mas com o segundo parágrafo adicionado a partir da experiência pessoal.
Larry Coleman
+1: Eu acredito que isso também é observado bastante cedo no The Pragmatic Programmer quase literalmente IIRC.
Steven Evers
definitivamente. Não há nada a abstrair com apenas um exemplo: assim que você tiver um segundo exemplo, poderá ver o que é comum (compartilhado) e o que não é, e pode abstrair!
Frank Shearar
Na minha opinião, uma amostra de duas é pequena demais para generalizar para o infinito. Em outras palavras, muito cedo demais.
2

Eric Lippert aponta três coisas que eu acho que se aplicam em seu artigo à prova de um design . Eu acho que se você seguir, estará em boa forma.

Primeiro: a generalidade prematura é cara.

Segundo: represente em seu modelo apenas as coisas que estão sempre no domínio do problema e cujas relações de classe são imutáveis.

Terceiro: mantenha suas políticas afastadas de seus mecanismos.

Conrad Frix
fonte
1

Depende do motivo pelo qual você está codificando, o objetivo do seu projeto. Se o valor do seu código é que resolve um problema concreto, você deseja fazer isso e passar para o próximo problema. Se houver coisas rápidas e fáceis que você possa fazer para facilitar as coisas para futuros usuários do código (incluindo você mesmo), faça todos os meios possíveis.

Por outro lado, há casos em que o código que você está escrevendo serve a um propósito mais genérico. Por exemplo, quando você está escrevendo uma biblioteca para outros programadores, que a usarão em uma ampla variedade de aplicativos. Os usuários em potencial são desconhecidos e você não pode perguntar exatamente o que eles querem. Mas você deseja tornar sua biblioteca amplamente útil. Nesses casos, eu gastaria mais tempo tentando oferecer suporte à solução geral.

Rob Weir
fonte
0

Sou um grande fã do princípio do KISS.

Eu me concentro em fornecer o que me pedem para fazer, e não na melhor solução. Eu tive que abandonar o perfeccionismo (e o TOC), porque isso me deixou infeliz.

Pablo
fonte
Por que a aversão ao perfeccionismo? (Eu não votei em você a propósito)
Orbling
Não apontar para a perfeição funciona para mim. Por quê? Porque eu sei que meus programas nunca serão perfeitos. Não existe uma definição padrão de perfeição, por isso optei por fornecer algo que funcione bem.
Pablo
-1

onde a Abstração está no seu ombro direito e o Resolva-estúpido, fica à esquerda.

Não concordo com "resolva estúpido" , acho que pode ser mais "resolva com inteligência" .

O que é mais inteligente:

  • Escrever uma solução generalizada complexa que possa suportar vários casos
  • Escrevendo código curto e effecient que resolve o problema na mão e é fácil de manter, e que pode ser estendido no futuro IF for necessário.

A opção padrão deve ser a segunda. A menos que você possa demonstrar uma necessidade de geração.

A solução generalizada deve ser apenas quando você souber que é algo que será usado em vários projetos / casos diferentes.

Normalmente, acho que as soluções generalizadas são melhor servidas como "Código da Biblioteca Principal"

Noite escura
fonte
Se você pudesse fazer todas as suposições aqui, não teria um problema. Curto e fácil de manter são mutuamente exclusivos. Se você souber que algo será reutilizado, a solução geral resolveria isso.
JeffO 03/01
@ Jeff Eu não tenho certeza eu entendo o seu comentário ..
Darknight
Você tirou o "Resolva Estúpido" fora de contexto.
Bryan Harrington