Recentemente, na minha empresa, tivemos um pouco de debate sobre abstração versus simplicidade. Uma escola de pensamento que eu caracterizaria como "SECA e abstração não pode fazer mal" e leva a códigos como este:
def make_foo_binary(binaryName, objFiles, fooLibsToLinkAgainst)
make_exe_task(binaryName, objFiles.ext('.o'), fooLibsToLinkAgainst)
end
e isto:
class String
def escape_space
return self.gsub(' ', '\ ')
end
end
Meu ponto de vista é que a criação de uma abstração como essa, usada apenas em um lugar, torna o código menos legível, pois você está substituindo uma chamada de função com a qual o leitor está familiarizado (gsub) por outro que eles nunca viram before (escape_space), que eles precisarão ler se quiserem entender como o código realmente funciona. A substituição é essencialmente descrita em inglês ("escape space") e o inglês é notoriamente vago. Por exemplo, sem olhar para a definição, você não sabe se ela escapa de todo o espaço em branco ou apenas do caractere de espaço.
Há muita escrita que canta os louvores de DRY e abstração. Alguém está ciente das fontes que descrevem os limites da abstração? Que cantam louvores e discutem as pragmáticas de manter o código simples?
Edit: Eu posso encontrar textos que incentivam a simplicidade na vida ou na escrita (em inglês), por exemplo, "Simplifique, Simplifique!" ou "A escrita vigorosa de Strunk and White é concisa". Onde é o equivalente para a programação?
fonte
Respostas:
Claro: Joel Spolsky (você pode ter ouvido falar dele) diz :
Além disso, veja KISS e YAGNI, que Jeff Atwood discute :
(veja o link para a cotação exata)
Minha interpretação destas citações:
É realmente difícil criar boas abstrações - é muito mais fácil criar coisas ruins.
Se você adicionar uma abstração para a qual não há necessidade ou está errada, agora você tem um código extra que precisa ser testado, depurado e mantido. E se você precisar voltar e refatorar, agora terá mais peso morto arrastando-o para baixo.
fonte
Não estou familiarizado com o idioma ou o tempo de execução dos seus exemplos, mas
gsub
não significa nada para mim. No entanto,escape_space
é muito mais significativo. Discordo completamente do seu argumento de que chamadas de funções familiares não devem ser substituídas por chamadas para funções que nunca haviam visto antes . Se eu levasse esse argumento ao extremo, nunca se deve adicionar uma nova função a um programa: ele sempre abstrai as chamadas de funções familiares e sempre as substitui por essa nova chamada desconhecida definida pelo usuário.Um desenvolvedor nunca deve ter que ler o código para entender como ele funciona. Antes de tudo, ele ou ela não deve se preocupar com o funcionamento, mas deve entender como usá-lo e quais efeitos ela tem. Se não for esse o caso, existe um problema com o nome e a assinatura da função ou sua documentação.
Para mim, o objetivo do resumo é remover detalhes internos e fornecer uma interface mais limpa para algumas funcionalidades. No futuro, o funcionamento interno da
escape_space
função poderá ser alterado ou substituído. Não ligo para que alguma funçãogsub
seja chamada com dois argumentos. Eu me importo que meus espaços sejam escapados. Então isso torna a abstração útil.No meu idioma favorito, C #, eu sempre adiciono documentação a todas as minhas funções (inclusive privadas), descrevendo coisas como a função, uso, unidades usadas, exceções lançadas, contrato de entrada e tipos. Em seguida, com o IntelliSense do Visual Studio, qualquer desenvolvedor pode ver mais claramente o que a função faz sem ler o código . Sem uma ferramenta como o IntelliSense, os desenvolvedores terão que ser mais específicos em seus nomes de função ou manter a documentação adicional em algum lugar facilmente acessível.
Não acho que as abstrações devam ser limitadas e não conheço essa fonte.
fonte
sed
e vim:s
), 'g' é usado como "todos"gsub
,atoi
ewcstombs
são muito piores do que o nomeescape_space
."something".gsub(' ', '\ ')
sabe exatamente qual é o resultado que está obtendo. Escapar de espaços não é algo que vejo com muita frequência. Está usando o caractere `\` ou algum outro caractere de escape? Escapa de novas linhas? Sem mencionar que é um monkeypatch da classe String padrão, que é considerada uma prática ruim por certos desenvolvedores.Ao falar sobre abstrações, acho importante definir dois termos: complexidade inerente e incidental. Complexidade inerente é a complexidade do problema que a abstração está resolvendo, complexidade incidental é a complexidade que a abstração está ocultando.
Boas abstrações são aquelas que escondem muita complexidade incidental e tentam resolver os problemas certos, diminuindo sua complexidade inerente. Eu acho que o que você está ficando frustrado aqui são abstrações muito superficiais; eles não escondem muita complexidade incidental e é tão difícil (se não mais) entender as abstrações quanto entender suas implementações.
fonte
Minha resposta vai se afastar mais da experiência pessoal, mas uma pesquisa rápida encontrou este blog , que parece entrar em muitos detalhes sobre quando a abstração vai longe demais (não apenas a entrada vinculada, mas existem várias relacionadas).
Basicamente, a abstração pode ser uma coisa boa. No entanto, como qualquer outra coisa, pode-se deixar levar. Seus exemplos são bons, onde a abstração foi longe demais. Você está criando "repasses" que basicamente nada fazem além de renomear a função em questão.
Agora, no caso de funções personalizadas, isso pode ser necessário, se você deseja descontinuar um esquema de nomeação antigo em favor de um novo (como alterar escape_space para escapeSpace ou escWhitespace, ou o que for). Para funções de biblioteca, no entanto? Exagero. Sem mencionar contraproducente. O que você ganhou ao fazer essa abstração? Se isso é tudo o que está fazendo, então tudo o que você ganhou é a dor de cabeça de ter que lembrar que a função e passar esse conhecimento para alguém (que, com funções de biblioteca, os desenvolvedores são ou já está ciente deles, ou deveria ser capaz de escrever
ruby gsub
em Google e descubra o que isso significa).Eu recomendo ser mais inteligente ao determinar quando abstrair algo. Por exemplo, coisas com mais de 2 a 3 linhas e usadas pelo menos duas vezes (mesmo que haja pequenas diferenças, muitas vezes, essas diferenças podem ser parametrizadas), geralmente são boas candidatas à abstração. Às vezes, considerarei grandes blocos de código que fazem uma coisa específica em uma função maior, para facilitar a leitura e o princípio "trabalho único", e abstraí-los para funções protegidas dentro da mesma classe (geralmente no imediato vizinhança da função que a utiliza).
Além disso, não esqueça o YAGNI (você não precisará dele) e a otimização prematura. Isso está sendo usado atualmente mais de uma vez? Não? Então não se preocupe em abstraí-lo agora (a menos que isso crie um ganho substancial de legibilidade). "Mas podemos precisar disso mais tarde!" Depois, abstraia-o quando você realmente precisar dele em outro lugar. Até lá, você acaba com ponteiros para ponteiros, sem nenhum benefício real.
Do outro lado desta moeda, quando você abstrair algo, faça-o assim que a necessidade se tornar evidente. É mais fácil mover e ajustar dois ou três blocos de código do que 15. De um modo geral, assim que me vejo copiando blocos de código de outro lugar ou escrevendo algo que parece muito familiar, começo a pensar em como abstraí-lo.
fonte
Primeiro, dê uma olhada neste exemplo de Robert C. Martin:
http://blog.objectmentor.com/articles/2009/09/11/one-thing-extract-till-you-drop
A razão para esse tipo de refatoração é conseqüentemente criar abstrações para criar funções muito pequenas, de modo que cada função faça apenas uma coisa . Isso leva a funções como as que você nos mostrou acima. Eu acredito que isso é uma coisa boa - ajuda você a criar código onde cada função chamada dentro de outra função está no mesmo nível de abstração.
Se você acha que usar nomes como
make_foo_binary
ouescape_space
tornar o código menos legível do que como consequência, tente escolher nomes melhores (por exemploescape_space_chars_with_backslash
:) em vez de abandonar a abstração.fonte