Certo ou errado, atualmente acredito que sempre devo tentar tornar meu código o mais robusto possível, mesmo que isso signifique adicionar código / verificações redundantes que sei que não serão úteis no momento, mas eles pode ser x quantidade de anos abaixo da linha.
Por exemplo, atualmente estou trabalhando em um aplicativo para dispositivos móveis que possui este código:
public static CalendarRow AssignAppointmentToRow(Appointment app, List<CalendarRow> rows)
{
//1. Is rows equal to null? - This will be the case if this is the first appointment.
if (rows == null) {
rows = new List<CalendarRow> ();
}
//2. Is rows empty? - This will be the case if this is the first appointment / some other unknown reason.
if(rows.Count == 0)
{
rows.Add (new CalendarRow (0));
rows [0].Appointments.Add (app);
}
//blah...
}
Observando especificamente a seção dois, sei que se a seção um for verdadeira, a seção dois também será verdadeira. Não consigo pensar em nenhuma razão para o fato de a seção um ser falsa e a segunda avaliar verdadeira, o que torna a segunda if
afirmação redundante.
No entanto, pode haver um caso no futuro em que essa segunda if
declaração seja realmente necessária e por um motivo conhecido.
Algumas pessoas podem olhar para isso inicialmente e pensar que estou programando com o futuro em mente, o que obviamente é uma coisa boa. Mas conheço algumas instâncias em que esse tipo de código me "ocultou" erros. Isso significa que demorei ainda mais para descobrir por que a função xyz
está funcionando abc
quando realmente deveria estar def
.
Por outro lado, também houve vários casos em que esse tipo de código tornou muito, muito mais fácil aprimorar o código com novos comportamentos, porque não preciso voltar atrás e garantir que todas as verificações relevantes estejam em vigor.
Há algum gerais regra de polegar diretrizes para esse tipo de código? (Eu também estaria interessado em saber se isso seria considerado uma boa ou má prática?)
NB: Isso pode ser considerado semelhante a esta pergunta, mas , diferentemente dessa pergunta, eu gostaria de uma resposta, assumindo que não há prazos.
TLDR: Devo ir ao ponto de adicionar código redundante para torná-lo potencialmente mais robusto no futuro?
if(rows.Count == 0)
que nunca acontecerá, poderá gerar uma exceção quando isso acontecer - e verificar por que sua suposição se enganou.rows
seria nulo? Nunca há um bom motivo, pelo menos no .NET, para uma coleção ser nula. Vazio , claro, mas não nulo . Eu lançaria uma exceção serows
for nulo, porque significa que há um lapso na lógica do chamador.Respostas:
Como exercício, primeiro vamos verificar sua lógica. Embora, como veremos, você tenha problemas maiores que qualquer problema lógico.
Chame a primeira condição A e a segunda condição B.
Você diz primeiro:
Ou seja: A implica B, ou, em termos mais básicos
(NOT A) OR B
E depois:
Ou seja:
NOT((NOT A) AND B)
. Aplique a lei de Demorgan para obter(NOT B) OR A
que B implica A.Portanto, se as duas afirmações forem verdadeiras, A implica B e B implica A, o que significa que elas devem ser iguais.
Portanto, sim, as verificações são redundantes. Você parece ter quatro caminhos de código no programa, mas na verdade você tem apenas dois.
Então agora a pergunta é: como escrever o código? A verdadeira questão é: qual é o contrato declarado do método ? A questão de saber se as condições são redundantes é um arenque vermelho. A verdadeira questão é "eu projetei um contrato sensato e meu método implementa claramente esse contrato?"
Vejamos a declaração:
É público, por isso deve ser robusto a dados ruins de chamadores arbitrários.
Ele retorna um valor, portanto deve ser útil pelo seu valor de retorno, não pelo efeito colateral.
E, no entanto, o nome do método é um verbo, sugerindo que é útil por seu efeito colateral.
O contrato do parâmetro da lista é:
Este contrato é insano . Imagine escrever a documentação para isso! Imagine escrever casos de teste!
Meu conselho: recomeçar. Essa API possui uma interface de máquina de doces escrita por toda parte. (A expressão é de uma história antiga sobre as máquinas de doces da Microsoft, onde os preços e as seleções são números de dois dígitos, e é super fácil digitar "85", que é o preço do item 75, e você obtém Fato curioso: sim, fiz isso por engano quando estava tentando extrair chiclete de uma máquina de venda automática da Microsoft!)
Veja como criar um contrato sensato:
Torne seu método útil para o efeito colateral ou o valor de retorno, não para os dois.
Não aceite tipos mutáveis como entradas, como listas; se você precisar de uma sequência de informações, use um IEnumerable. Leia apenas a sequência; não grave em uma coleção passada, a menos que seja muito claro que este é o contrato do método. Ao usar o IEnumerable, você envia uma mensagem para o chamador informando que não irá alterar a coleção deles.
Nunca aceite nulos; uma sequência nula é uma abominação. Exija que o chamador passe uma sequência vazia, se isso fizer sentido, nunca nulo.
Falha imediatamente se o interlocutor violar seu contrato, para ensiná-lo que você está falando sério e para que eles detectem seus erros nos testes, não na produção.
Projete o contrato primeiro para ser o mais sensato possível e, em seguida, implemente claramente o contrato. Esse é o caminho para um design à prova de futuro.
Agora, conversamos apenas sobre o seu caso específico e você fez uma pergunta geral. Então, aqui estão alguns conselhos gerais adicionais:
Se houver um fato que você como desenvolvedor possa deduzir, mas o compilador não puder, use uma asserção para documentar esse fato. Se outro desenvolvedor, como o futuro você ou um de seus colegas de trabalho, violar essa suposição, a afirmação informará.
Obtenha uma ferramenta de cobertura de teste. Verifique se seus testes abrangem todas as linhas de código. Se houver um código descoberto, você terá um teste ausente ou um código morto. O código morto é surpreendentemente perigoso, porque geralmente não se destina a estar morto! A incrivelmente terrível falha de segurança da Apple que "vai falhar" há alguns anos vem imediatamente à mente.
Obtenha uma ferramenta de análise estática. Heck, pegue vários; toda ferramenta tem sua própria especialidade específica e nenhuma é um superconjunto das outras. Preste atenção ao informar que há um código inacessível ou redundante. Novamente, esses são prováveis erros.
Se parece que estou dizendo: primeiro, projete bem o código e, segundo, teste o que há de errado para garantir que esteja correto hoje, é o que estou dizendo. Fazer essas coisas tornará muito mais fácil lidar com o futuro; a parte mais difícil do futuro é lidar com todo o código de máquina de doces de buggy que as pessoas escreveram no passado. Faça o que está certo hoje e os custos serão mais baixos no futuro.
fonte
CompareExchange
sem essa capacidade!), E mesmo em cenários não concorrentes, algo como "adicione um registro, se não existir, e retorne o pass-in registro ou o que existia "pode ser mais conveniente do que qualquer abordagem que não empregue um efeito colateral e um valor de retorno.O que você está fazendo no código que está mostrando acima não é tanto uma prova futura, mas também uma codificação defensiva .
Ambas as
if
instruções testam coisas diferentes. Ambos são testes adequados, dependendo de suas necessidades.A Seção 1 testa e corrige um
null
objeto. Nota lateral: Criar a lista não cria nenhum item filho (por exemplo,CalendarRow
).A Seção 2 testa e corrige erros de usuário e / ou implementação. Só porque você tem um
List<CalendarRow>
não significa que você possui itens na lista. Usuários e implementadores farão coisas que você não pode imaginar apenas porque têm permissão para fazê-lo, faça sentido para você ou não.fonte
Eu acho que essa questão é basicamente a gosto. Sim, é uma boa idéia escrever código robusto, mas o código no seu exemplo é uma leve violação do princípio do KISS (como muitos desses códigos "à prova de futuro" serão).
Eu, pessoalmente, provavelmente não me incomodaria em tornar o código à prova de balas para o futuro. Não conheço o futuro e, como tal, qualquer código "à prova de balas" está fadado a falhar miseravelmente quando o futuro chegar de qualquer maneira.
Em vez disso, prefiro uma abordagem diferente: faça as suposições que você tornar explícitas usando a
assert()
macro ou recursos semelhantes. Dessa forma, quando o futuro chegar, ele lhe dirá exatamente onde suas suposições não se sustentam mais.fonte
Outro princípio que você pode querer pensar é a idéia de falhar rapidamente . A idéia é que, quando algo der errado em seu programa, você deseja interromper o programa imediatamente, pelo menos enquanto o estiver desenvolvendo antes de liberá-lo. Sob esse princípio, você deseja fazer muitas verificações para garantir que suas suposições sejam válidas, mas considere seriamente que seu programa pare de funcionar sempre que as suposições forem violadas.
Para colocá-lo com ousadia, se houver um pequeno erro no seu programa, você deseja travar completamente enquanto assiste!
Isso pode parecer contra-intuitivo, mas permite descobrir erros o mais rápido possível durante o desenvolvimento de rotina. Se você está escrevendo um pedaço de código e acha que está concluído, mas falha ao testá-lo, não há dúvida de que você ainda não terminou. Além disso, a maioria das linguagens de programação oferece excelentes ferramentas de depuração mais fáceis de usar quando o programa falha completamente, em vez de tentar fazer o melhor possível após um erro. O maior exemplo mais comum é que, se você travar o programa lançando uma exceção não tratada, a mensagem de exceção informará uma quantidade incrível de informações sobre o bug, incluindo qual linha de código falhou e o caminho através do código que o programa assumiu. seu caminho para essa linha de código (o rastreamento da pilha).
Para mais pensamentos, leia este pequeno ensaio: Não pregue seu programa na posição vertical
Isso é relevante para você, porque é possível que, às vezes, as verificações que você está escrevendo estejam lá porque você deseja que seu programa continue em execução mesmo depois que algo der errado. Por exemplo, considere esta breve implementação da sequência de Fibonacci:
Isso funciona, mas e se alguém passar um número negativo para sua função? Não vai funcionar então! Portanto, você deseja adicionar uma verificação para garantir que a função seja chamada com uma entrada não negativa.
Pode ser tentador escrever a função assim:
No entanto, se você fizer isso, mais tarde, acidentalmente, chamar sua função de Fibonacci com uma entrada negativa, você nunca perceberá! Pior, seu programa provavelmente continuará em execução, mas começará a produzir resultados sem sentido, sem fornecer pistas sobre onde algo deu errado. Esses são os tipos mais difíceis de corrigir.
Em vez disso, é melhor escrever um cheque como este:
Agora, se você chamar acidentalmente a função Fibonacci com uma entrada negativa, seu programa será interrompido imediatamente e informará que algo está errado. Além disso, fornecendo um rastreamento de pilha, o programa informará qual parte do seu programa tentou executar a função Fibonacci incorretamente, fornecendo um excelente ponto de partida para depurar o que está errado.
fonte
Você deve adicionar código redundante? Não.
Mas, o que você descreve não é um código redundante .
O que você descreve é programar defensivamente contra o código de chamada que viola as pré-condições de sua função. Se você faz isso ou simplesmente deixa para os usuários a leitura da documentação e evita essas violações, é totalmente subjetivo.
Pessoalmente, acredito muito nessa metodologia, mas, como em tudo, é preciso ter cuidado. Tomemos, por exemplo, C ++ 's
std::vector::operator[]
. Deixando de lado a implementação do modo de depuração do VS por um momento, essa função não executa verificação de limites. Se você solicitar um elemento que não existe, o resultado será indefinido. Cabe ao usuário fornecer um índice vetorial válido. Isso é bastante deliberado: você pode "ativar" a verificação de limites adicionando-a no local da chamada, mas se aoperator[]
implementação fosse executada, não seria possível "desativar". Como uma função de nível bastante baixo, isso faz sentido.Mas se você estivesse escrevendo uma
AddEmployee(string name)
função para alguma interface de nível superior, seria de esperar que essa função gerasse pelo menos uma exceção se você fornecesse um vazioname
, além de essa pré-condição ser documentada imediatamente acima da declaração da função. Você pode não estar fornecendo entrada não autorizada do usuário para essa função hoje, mas torná-la "segura" dessa maneira significa que qualquer violação de pré-condição que aparecer no futuro pode ser facilmente diagnosticada, em vez de potencialmente desencadear uma cadeia de dominós de difícil acesso. detectar erros. Isso não é redundância: é diligência.Se eu tivesse que criar uma regra geral (embora, como regra geral, tente evitá-las), diria que uma função satisfaz qualquer um dos seguintes:
... pode se beneficiar da programação defensiva. Em outros casos, você ainda pode gravar os
assert
íons disparados durante o teste, mas estão desabilitados nas compilações de versão, para aumentar ainda mais sua capacidade de encontrar bugs.Este tópico é mais explorado na Wikipedia ( https://en.wikipedia.org/wiki/Defensive_programming ).
fonte
Dois dos dez mandamentos de programação são relevantes aqui:
Você não deve assumir que a entrada está correta
Não criarás código para uso futuro
Aqui, verificar nulo não é "criar código para uso futuro". Criar código para uso futuro é algo como adicionar interfaces, porque você acha que elas podem ser úteis "algum dia". Em outras palavras, o mandamento é não adicionar camadas de abstração, a menos que sejam necessárias no momento.
A verificação de nulo não tem nada a ver com o uso futuro. Tem a ver com o mandamento nº 1: não assuma que a entrada estará correta. Nunca assuma que sua função receberá algum subconjunto de entrada. Uma função deve responder de uma maneira lógica, não importa o quão falsa e bagunçada as entradas sejam.
fonte
Thou shall not make code for future use
entraram em problemas de manutenção mais cedo , apesar da lógica intuitiva do mandamento. Descobri, na codificação da vida real, que o mandamento só é eficaz no código em que você controla a lista de recursos e os prazos, além de garantir que não precise de um código com aparência futura para alcançá-los ... ou seja, nunca.A definição de 'código redundante' e 'YAGNI' geralmente depende de quanto tempo você olha para o futuro.
Se você tiver um problema, tenderá a escrever código futuro de forma a evitar esse problema. Outro programador que não enfrentou esse problema específico pode considerar um exagero redundante no seu código.
Minha sugestão é acompanhar quanto tempo você gasta em 'coisas que ainda não erraram', se as cargas forem carregadas e os colegas compartilharem recursos mais rapidamente do que você, depois diminua-o.
No entanto, se você é como eu, espero que você tenha digitado tudo 'por padrão' e realmente não demorou mais.
fonte
É uma boa idéia documentar quaisquer suposições sobre parâmetros. E é uma boa ideia verificar se o código do seu cliente não viola essas suposições. Eu faria isso:
[Supondo que seja C #, o Assert pode não ser a melhor ferramenta para o trabalho, pois não é compilado no código liberado. Mas isso é um debate para outro dia.]
Por que isso é melhor do que o que você escreveu? Seu código faz sentido se, em um futuro em que seu cliente for alterado, quando o cliente passar em uma lista vazia, a coisa certa a fazer será adicionar uma primeira linha e adicionar o aplicativo aos seus compromissos. Mas como você sabe que será esse o caso? É melhor fazer menos suposições agora sobre o futuro.
fonte
Estime o custo de adicionar esse código agora . Será relativamente barato, porque tudo está fresco em sua mente, então você poderá fazer isso rapidamente. É necessário adicionar testes de unidade - não há nada pior do que usar algum método um ano depois, ele não funciona e você descobre que ele foi quebrado desde o início e nunca funcionou.
Estime o custo de adicionar esse código quando necessário. Vai ser mais caro, porque você precisa voltar ao código, lembrar de tudo, e é muito mais difícil.
Estime a probabilidade de que o código adicional seja realmente necessário. Então faça as contas.
Por outro lado, código cheio de suposições "X nunca acontecerá" é horrível para depuração. Se algo não funciona como pretendido, significa um erro estúpido ou uma suposição errada. O seu "X nunca acontecerá" é uma suposição e, na presença de um bug, é suspeito. O que força o próximo desenvolvedor a perder tempo com isso. Geralmente é melhor não confiar em tais suposições.
fonte
A questão principal aqui é "o que acontecerá se você fizer / não"?
Como outros já apontaram, esse tipo de programação defensiva é bom, mas também é ocasionalmente perigoso.
Por exemplo, se você fornecer um valor padrão, estará mantendo o programa atualizado. Mas o programa agora pode não estar fazendo o que você deseja. Por exemplo, se ele gravar essa matriz em branco em um arquivo, agora você pode ter transformado seu bug de "falhas porque forneci nulo por acidente" para "limpa as linhas do calendário porque forneci nulo por acidente". (por exemplo, se você começar a remover coisas que não aparecem na lista da parte em que se lê "// blá")
A chave para mim é nunca corromper dados . Deixe-me repetir isso; NUNCA. CORROMPIDO. DADOS. Se o seu programa der uma exceção, você recebe um relatório de bug que pode corrigir; se gravar dados ruins em um arquivo que será posteriormente, você precisará semear o solo com sal.
Todas as suas decisões "desnecessárias" devem ser tomadas com essa premissa em mente.
fonte
O que você está lidando aqui é basicamente uma interface. Adicionando o comportamento "quando a entrada é
null
, inicialize a entrada", você estendeu efetivamente a interface do método - agora, em vez de sempre operar em uma lista válida, você "corrigiu" a entrada. Se essa é a parte oficial ou não oficial da interface, você pode apostar que alguém (provavelmente você) vai usar esse comportamento.As interfaces devem ser mantidas simples e devem ser relativamente estáveis - especialmente em algo como um
public static
método. Você tem um pouco de liberdade nos métodos privados, especialmente nos métodos de instância privada. Ao estender implicitamente a interface, você tornou seu código mais complexo na prática. Agora, imagine que você realmente não deseja usar esse caminho de código - para evitá-lo. Agora você tem um pouco de código não testado que finge fazer parte do comportamento do método. E posso dizer agora que provavelmente há um erro: quando você passa uma lista, essa lista é alterada pelo método. No entanto, se não o fizer, crie um locallista e jogue fora depois. Esse é o tipo de comportamento inconsistente que fará você chorar em meio ano, enquanto tenta rastrear um bug obscuro.Em geral, a programação defensiva é uma coisa bastante útil. Mas os caminhos de código para as verificações defensivas devem ser testados, como qualquer outro código. Em um caso como esse, eles estão complicando seu código sem motivo, e eu optaria por uma alternativa como essa:
Você não deseja uma entrada onde
rows
seja nulo e deseja tornar o erro óbvio para todos os seus chamadores o mais rápido possível .Há muitos valores que você precisa analisar ao desenvolver software. Até a robustez em si é uma qualidade muito complexa - por exemplo, eu não consideraria seu cheque defensivo mais robusto do que lançar uma exceção. As exceções são bastante úteis para oferecer a você um local seguro para tentar novamente a partir de um local seguro - os problemas de corrupção de dados geralmente são muito mais difíceis de rastrear do que reconhecer um problema logo no início e manipulá-lo com segurança. No final, eles apenas tendem a lhe dar uma ilusão de robustez e, um mês depois, você percebe que um décimo dos seus compromissos se foi, porque você nunca notou que uma lista diferente foi atualizada. Ai.
Certifique-se de distinguir entre os dois. A programação defensiva é uma técnica útil para detectar erros em um local onde eles são os mais relevantes, ajudando significativamente seus esforços de depuração e com um bom tratamento de exceções, evitando a "corrupção furtiva". Falhe cedo, falhe rápido. Por outro lado, o que você está fazendo é mais como "ocultação de erros" - você está manipulando entradas e fazendo suposições sobre o que o chamador quis dizer. Isso é muito importante para o código voltado ao usuário (por exemplo, verificação ortográfica), mas você deve ser cauteloso ao ver isso com o código voltado para o desenvolvedor.
O principal problema é que, qualquer que seja a abstração que você fizer, ela vazará ("Eu queria escrever mais do que isso! Verificador estúpido de correção ortográfica!"), E o código para lidar com todos os casos e correções especiais ainda será o código precisa manter e entender e codificar que você precisa testar. Compare o esforço de garantir que uma lista não nula seja aprovada com a correção do bug que você tem um ano depois na produção - não é uma boa opção. Em um mundo ideal, você deseja que todo método funcione exclusivamente com sua própria entrada, retornando um resultado e modificando nenhum estado global. Claro, no mundo real, você encontrará muitos casos em que isso não éa solução mais simples e clara (por exemplo, ao salvar um arquivo), mas acho que manter os métodos "puros" quando não há motivo para ler ou manipular o estado global facilita o raciocínio do código. Também tende a fornecer pontos mais naturais para dividir seus métodos :)
Isso não significa que tudo que é inesperado deve causar uma falha no aplicativo, pelo contrário. Se você usar bem as exceções, elas naturalmente formarão pontos seguros de tratamento de erros, onde você poderá restaurar o estado estável do aplicativo e permitir que o usuário continue o que está fazendo (idealmente, evitando qualquer perda de dados para o usuário). Nesses pontos de manipulação, você verá oportunidades para corrigir os problemas ("Nenhum número de pedido 2212 encontrado. Você quis dizer 2212b?") Ou fornecer ao usuário o controle ("Erro ao conectar-se ao banco de dados. Tente novamente?"). Mesmo quando essa opção não estiver disponível, pelo menos, você terá a chance de que nada seja corrompido - comecei a apreciar o código que usa
using
etry
...finally
muito mais do quetry
...catch
, oferece muitas oportunidades para manter os invariantes, mesmo em condições excepcionais.Os usuários não devem perder seus dados e trabalho. Isso ainda precisa ser equilibrado com os custos de desenvolvimento, etc., mas é uma orientação geral muito boa (se os usuários decidirem comprar seu software ou não - o software interno geralmente não tem esse luxo). Até o travamento de todo o aplicativo se torna muito menos problemático se o usuário puder simplesmente reiniciar e voltar ao que estava fazendo. Isso é realmente robusto - o Word salva seu trabalho o tempo todo sem danificar o documento em disco e oferece uma opçãorestaurar essas alterações após reiniciar o Word após uma falha. É melhor do que um erro não em primeiro lugar? Provavelmente não - embora não se esqueça que o trabalho gasto na captura de um bug raro pode ser melhor gasto em todos os lugares. Mas é muito melhor do que as alternativas - por exemplo, um documento corrompido no disco, todo o trabalho desde a última vez que foi salvo, documento substituído automaticamente por alterações antes da falha, que eram apenas Ctrl + A e Delete.
fonte
Vou responder isso com base na sua suposição de que um código robusto beneficiará você "daqui a alguns anos". Se os benefícios a longo prazo forem sua meta, eu priorizaria o design e a manutenção, em vez da robustez.
A troca entre design e robustez é tempo e foco. A maioria dos desenvolvedores prefere ter um conjunto de códigos bem projetados, mesmo que isso signifique passar por alguns pontos problemáticos e fazer alguma condição adicional ou tratamento de erros. Após alguns anos de uso, os locais que você realmente precisa provavelmente foram identificados pelos usuários.
Supondo que o design tenha a mesma qualidade, menos código é mais fácil de manter. Isso não significa que estamos em melhor situação se você deixar problemas conhecidos por alguns anos, mas adicionar coisas que você sabe que não precisa dificulta. Todos analisamos o código legado e descobrimos partes desnecessárias. Você precisa ter um código de alteração de confiança de alto nível que funcione há anos.
Portanto, se você acha que seu aplicativo foi projetado da melhor maneira possível, é fácil de manter e não possui bugs, encontre algo melhor para fazer do que adicionar o código que você não precisa. É o mínimo que você pode fazer por respeito a todos os outros desenvolvedores que trabalham longas horas em recursos inúteis.
fonte
Não, você não deveria. E você está realmente respondendo sua própria pergunta quando afirma que essa maneira de codificar pode ocultar bugs . Isso não tornará o código mais robusto - ao contrário, tornará mais propenso a erros e tornará a depuração mais difícil.
Você declara suas expectativas atuais sobre o
rows
argumento: ou é nulo ou tem pelo menos um item. Portanto, a pergunta é: É uma boa idéia escrever o código para lidar adicionalmente com o terceiro caso inesperado, onde nãorows
há itens?A resposta é não. Você sempre deve lançar uma exceção no caso de entrada inesperada. Considere o seguinte: Se algumas outras partes do código quebram a expectativa (ou seja, o contrato) do seu método, isso significa que há um erro . Se houver um erro, você deseja conhecê-lo o mais cedo possível para poder corrigi-lo, e uma exceção o ajudará a fazer isso.
O que o código atualmente faz é adivinhar como se recuperar de um bug que pode ou não existir no código. Mas mesmo se houver um bug, você não pode saber como se recuperar totalmente dele. Erros por definição têm consequências desconhecidas. Talvez algum código de inicialização não tenha sido executado conforme o esperado, podendo ter muitas outras consequências além da linha que está faltando.
Portanto, seu código deve ficar assim:
Nota: Existem alguns casos específicos em que faz sentido "adivinhar" como lidar com entradas inválidas em vez de apenas lançar uma exceção. Por exemplo, se você lida com entrada externa, não tem controle. Os navegadores da Web são um exemplo infame, porque tentam lidar com qualquer tipo de entrada incorreta e inválida. Mas isso só faz sentido com entrada externa, não com chamadas de outras partes do programa.
Edit: Algumas outras respostas afirmam que você está fazendo programação defensiva . Discordo. Programação defensiva significa que você não confia automaticamente na entrada para ser válida. Portanto, a validação de parâmetros (como acima) é uma técnica de programação defensiva, mas isso não significa que você deva alterar entradas inesperadas ou inválidas ao adivinhar. A abordagem defensiva robusta é validar a entrada e, em seguida, lançar uma exceção em caso de entrada inesperada ou inválida.
fonte
Você não deve adicionar código redundante a qualquer momento.
Você não deve adicionar código necessário apenas no futuro.
Você deve garantir que seu código se comporte bem, não importa o que aconteça.
A definição de "comporte-se bem" depende de suas necessidades. Uma técnica que gosto de usar são as exceções de "paranóia". Se tenho 100% de certeza de que um determinado caso nunca pode acontecer, continuo programando uma exceção, mas o faço de uma maneira que a) diga claramente a todos que nunca espero que isso aconteça eb) seja claramente exibido e registrado e portanto, não leva à corrupção crescente mais tarde.
Exemplo de pseudocódigo:
Isso comunica claramente que eu tenho 100% de certeza de que sempre posso abrir, gravar ou fechar o arquivo, ou seja, não vou ao ponto de criar um tratamento de erro elaborado. Mas se (não: quando) não consigo abrir o arquivo, meu programa ainda falhará de maneira controlada.
Obviamente, a interface do usuário não exibirá essas mensagens para o usuário, elas serão registradas internamente juntamente com o rastreamento da pilha. Novamente, essas são exceções internas de "Paranoia", que apenas garantem que o código "pare" quando algo inesperado acontecer. Este exemplo é um pouco complicado, na prática, é claro, eu implementaria o tratamento real de erros ao abrir arquivos, pois isso acontece regularmente (nome de arquivo errado, pendrive USB somente leitura, o que for).
Um termo de pesquisa relacionado muito importante seria "falhar rápido", conforme observado em outras respostas e é muito útil para criar software robusto.
fonte
Há muitas respostas muito complicadas aqui. Você provavelmente fez essa pergunta porque não se sentiu bem com esse código, mas não sabia ao certo por que ou como corrigi-lo. Portanto, minha resposta é que o problema é muito provável na estrutura do código (como sempre).
Primeiro, o cabeçalho do método:
Atribuir compromisso a que linha? Isso deve ficar claro imediatamente na lista de parâmetros. Sem qualquer conhecimento, eu esperaria que os parâmetros de método para se parecer com isto:
(Appointment app, CalendarRow row)
.Em seguida, as "verificações de entrada":
Isso é besteira.
rows
para o método provavelmente está errada (veja o comentário acima), não deve ser responsabilidade do método chamadoAssignAppointmentToRow
para manipular linhas de outra maneira que não seja a atribuição do compromisso em algum lugar.Mas todo o conceito de atribuir compromissos em algum lugar é estranho (a menos que isso faça parte da GUI do código). Seu código parece conter (ou pelo menos tenta) uma estrutura de dados explícita representando um calendário (que é
List<CalendarRows>
<- que deve ser definido comoCalendar
algum lugar se você quiser seguir esse caminho, então você passariaCalendar calendar
para o seu método). Se você seguir esse caminho, eu esperariacalendar
que fosse preenchido com slots onde você coloca (atribui) os compromissos posteriormente (por exemplo,calendar[month][day] = appointment
seria o código apropriado). Mas você também pode optar por abandonar completamente a estrutura do calendário da lógica principal e apenas terList<Appointment>
onde osAppointment
objetos contêm atributos.date
. E então, se você precisar renderizar um calendário em algum lugar da GUI, poderá criar essa estrutura de 'calendário explícito' imediatamente antes da renderização.Como não conheço os detalhes do seu aplicativo, talvez isso não seja aplicável a você, mas essas duas verificações (principalmente a segunda) me dizem que algo está errado com a separação de preocupações no seu código.
fonte
Por uma questão de simplicidade, vamos supor que, eventualmente, você precisará desse código em N dias (nem mais tarde nem mais cedo) ou não precisará.
Pseudo-código:
Fatores para
C_maint
:C_maint
esperadoC_maint
. Este caso requer uma fórmula mais complexa, com mais vezes variáveis comoN
.Uma coisa tão grande que apenas pesa o código e pode ser necessária apenas em 2 anos com baixa probabilidade deve ser deixada de lado, mas uma coisinha que também produz asserções úteis e 50% que serão necessárias em 3 meses, deve ser implementada .
fonte