Pergunta

Eu desenvolvo e mantenho um aplicativo WinForms grande (500k+ LOC) escrito em C# 2.0.É multiusuário e atualmente está implantado em cerca de 15 máquinas.O desenvolvimento do sistema está em andamento (pode ser considerado um beta perpétuo) e muito pouco é feito para proteger os usuários de possíveis novos bugs que possam ser introduzidos em uma compilação semanal.

Por esse motivo, entre outros, comecei a depender muito de editar e continuar no depurador.Ajuda não apenas na busca e correção de bugs, mas em alguns casos também no desenvolvimento contínuo.Acho extremamente valioso poder executar código recém-escrito a partir do contexto de um aplicativo em execução - não há necessidade de recompilar e adicionar um ponto de entrada específico ao novo código (ter que adicionar opções de menu, botões, etc. o aplicativo e lembre-se de removê-los antes da próxima compilação de produção) - tudo pode ser experimentado e testado em tempo real sem interromper o processo.

Tenho tanta consideração por editar e continuar que escrevo ativamente código para ser totalmente compatível com ele.Por exemplo, evito:

  • Métodos anônimos e delegados inline (a menos que seja completamente impossível reescrever)
  • Métodos genéricos (exceto em código utilitário estável e imutável)
  • Direcionando projetos para 'Qualquer CPU' (ou seja,nunca executando em 64 bits)
  • Inicializando campos no ponto de declaração (a inicialização é movida para o construtor)
  • Escrevendo blocos enumeradores que usam yield (exceto no código utilitário)

Agora, estou plenamente ciente de que os novos recursos de linguagem em C# 3 e 4 são amplamente incompatíveis com editar e continuar (expressões lambda, LINQ, etc.).Esta é uma das razões pelas quais resisti em mover o projeto para uma versão mais recente do Framework.

Minha pergunta é se é uma boa prática evitar o uso dessas construções mais avançadas em favor de um código que seja muito, muito fácil de depurar.Existe legitimidade neste tipo de desenvolvimento ou é um desperdício?Além disso, o que é mais importante, alguma dessas construções (expressões lambda, métodos anônimos, etc.) incorre em sobrecargas de desempenho/memória que um código bem escrito e compatível com edição e continuação poderia evitar?... ou o funcionamento interno do compilador C # faz com que essas construções avançadas sejam executadas mais rapidamente do que o código 'expandido' escrito manualmente?

Foi útil?

Solução

Sem querer parecer banal - é uma boa prática escrever testes de unidade/integração, em vez de confiar na continuação de edição.

Dessa forma, você gasta o esforço uma vez, e todas as outras vezes são 'grátis' ...

Agora não estou sugerindo que você escreva unidades retrospectivamente para todo o seu código; Em vez disso, cada vez que você precisa corrigir um bug, comece escrevendo um teste (ou mais comumente vários testes) que prova a correção.

Como @Dave Swersky menciona nos comentários, o livro de Mchael Feathers, Trabalhando efetivamente com o código legado É um bom recurso (é legado 5 minutos depois que você o escreveu, certo?)

Então, sim, acho que é um erro evitar novos contutos C# a favor de permitir editar e continuar; Mas também acho que é um erro adotar novas construções apenas por causa disso, e especialmente se eles levarem a um código mais difícil de entender.

Outras dicas

Eu amo 'editar e continuar'. Acho que é um enorme facilitador para desenvolvimento/depuração interativa e também acho bastante irritante quando não funciona.

Se 'editar e continuar' ajudar sua metodologia de desenvolvimento, então, por todos os meios, fazem escolhas para facilitar, tendo em mente o valor do que você está desistindo.

Uma das minhas irritações é que editar qualquer coisa em uma função com expressões lambda quebra 'editar e continuar'. Se eu tropeçar o suficiente, posso escrever a expressão de Lambda. Estou em cima do muro com expressões Lambda. Eu posso fazer algumas coisas mais rapidamente com elas, mas elas não me economizam tempo se eu acabar escrevê -las mais tarde.

No meu caso, evito usar expressões Lambda quando realmente não preciso. Se eles atrapalharem, eu posso envolvê -los em uma função para que eu possa 'editar e continuar' o código que os usa. Se eles são gratuitos, posso escrevê -los.

Sua abordagem não precisa ser preto e branco.

Queria esclarecer um pouco essas coisas

É uma boa prática evitar o uso dessas construções mais avançadas em favor do código que é muito, muito fácil de depurar?

Editar e continuar não está realmente depurando, está se desenvolvendo. Faço essa distinção porque os novos recursos C# são muito desbastáveis. Cada versão do idioma adiciona suporte de depuração para novos recursos de idioma para torná -los o mais fácil possível depuração possível.

Tudo pode ser experimentado e testado em tempo real sem interromper o processo.

Esta afirmação é enganosa. É possível com a edição e continuar a verificar uma alteração corrige um problema muito específico. É muito mais difícil verificar se a mudança está correta e não quebra uma série de outros problemas. Ou seja, porque editar e continuar não modifica os binários no disco e, portanto, não permite itens como testes de unidade.

No geral, embora sim, acho que é um erro evitar novos contutos C# a favor de permitir editar e continuar. Editar e continuar é um ótimo recurso (realmente adorei quando o encontrei nos meus dias C ++). Mas o valor é um ajudante de servidor de produção não compensa os ganhos produtivos dos novos recursos C# IMHO.

Minha pergunta é se é uma boa prática evitar usar essas construções mais avançadas em favor do código que é muito, muito fácil de depurar

Eu argumentaria que sempre que você estiver se forçando a escrever código que é:

  1. Menos expressivo
  2. Mais tempo
  3. Repetido (de evitar métodos genéricos)
  4. Não portável (nunca depure e teste 64 bits ??!?!?)

Você está aumentando muito o custo geral de manutenção do que a perda da funcionalidade "Editar e continuar" no depurador.

Eu escreveria o melhor código possível, não o código que torna um recurso do seu trabalho de IDE.

Embora não haja nada inerentemente errado com sua abordagem, isso o limita à quantidade de expressividade entendida pelo IDE. Seu código se torna um reflexo de Está Os recursos, não o idioma, e, portanto, seu valor geral no mundo do desenvolvimento diminui porque você está se mantendo em aprender outras técnicas de melhoria da produtividade. Evitar o LINQ a favor da edição e da continuação parece uma enorme oportunidade para mim pessoalmente, mas o paradoxo é que você precisa ganhar alguma experiência com ela antes de se sentir assim.

Além disso, como foi mencionado em outras respostas, testar unidade seu código remove o precisar Para executar o aplicativo inteiro o tempo todo e, portanto, resolve seu dilema de uma maneira diferente. Se você não puder clicar com o botão direito do mouse no seu IDE e testar apenas as três linhas de código de que se preocupa, já está fazendo muito trabalho durante o desenvolvimento.

Você realmente deve introduzir a integração continua, o que pode ajudá -lo a encontrar e eliminar erros antes de implantar software. Especialmente grandes projetos (considero 500k bastante grandes) precisam de algum tipo de validação.

http://www.codinghorror.com/blog/2006/02/revisiting-edit-and-continue.html

Em relação à pergunta específica: não evite essas construções e não confie em suas habilidades de depuração louca - tente evitar bugs (em software implantado). Escreva testes de unidade.

Também trabalhei em grandes projetos beta permanentes.

Usei métodos anônimos e delegados embutidos para manter alguns bits relativamente simples de lógica de uso único perto de seu único local de uso.

Usei métodos e classes genéricos para reutilização e confiabilidade.

Inicializei classes em construtores da forma mais completa possível, para manter invariantes de classe e eliminar a possibilidade de bugs causados ​​por objetos em estados inválidos.

Usei blocos enumeradores para reduzir a quantidade de código necessária para criar uma classe enumeradora para algumas linhas.

Tudo isso é útil para manter um grande projeto em rápida mudança em um estado confiável.

Se não consigo editar e continuar, edito e começo de novo.Isso me custa alguns segundos na maioria das vezes, alguns minutos em casos desagradáveis.Vale a pena pelas horas que uma maior capacidade de raciocinar sobre o código e uma maior confiabilidade por meio da reutilização me salvam.

Farei muito para facilitar a localização de bugs, mas não se isso também facilitar a localização de bugs.

Você poderia tentar Desenvolvimento orientado a testes. Achei muito útil evitar usar o depurador. Você começa a partir de um novo teste (por exemplo, teste de unidade) e, em seguida, executa apenas este teste de unidade para verificar seu desenvolvimento - não precisa de todo o aplicativo em execução o tempo todo. E isso significa que você não precisa de editar e continuar!

Eu sei que o TDD é a palavra atual, mas realmente funciona para mim. Se eu precisar usar o depurador, tomo isso como um fracasso pessoal :)

Confiando na edição e cont. Parece que há muito pouco tempo gasto no design de novos recursos, muito menos nos testes de unidade. Acho isso ruim porque você provavelmente acaba fazendo muita depuração e correção de bugs, e às vezes as correções de bugs causam mais bugs, certo?

No entanto, é muito difícil julgar se você deve ou não usar os recursos do idioma ou não, porque isso também depende de muitos, muitos outros fatores: REQS do Projeto, Libere prazos, habilidades de equipe, custo de gerenciamento de código após refatorar, para citar alguns .

Espero que isto ajude!

O problema que você parece estar tendo é:

Demora muito para reconstruir o aplicativo, iniciar novamente e acesse a UI em que você está trabalhando.

Como todos disseram, os testes de unidade ajudarão a reduzir o número de vezes que você precisa executar seu aplicativo para encontrar/corrigir bugs no código da interface do usuário; No entanto, eles não ajudam em questões como o layout da interface do usuário.

No passado, escrevi um aplicativo de teste que carregará rapidamente a interface do usuário em que estou trabalhando e preenche -o com dados fictícios, para reduzir o tempo de ciclo.

Separando nenhum código da interface do usuário em outras classes que podem ser testadas com testes de unidade, permitirá que você use todos os construções C# nessas classes. Em seguida, você pode limitar os construtos em uso no código da interface do usuário.

Quando comecei a escrever muitos testes de unidade, meu uso de "Editar e Continuar" caiu, como dificilmente o uso além do código da interface do usuário.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top