O que define código robusto?

40

Meu professor continua referindo-se a esse exemplo Java quando fala de código "robusto":

if (var == true) {
    ...
} else if (var == false) {
    ...
} else {
    ...
}

Ele afirma que "código robusto" significa que seu programa leva em consideração todas as possibilidades, e que não existe erro - todas as situações são tratadas pelo código e resultam em estado válido, daí o "else".

Eu tenho dúvidas, no entanto. Se a variável é booleana, qual é o sentido de verificar um terceiro estado quando um terceiro estado é logicamente impossível?

"Não ter erro" também parece ridículo; até mesmo os aplicativos do Google mostram erros diretamente para o usuário, em vez de engoli-los silenciosamente ou, de alguma forma, considerá-los como um estado válido. E é bom - eu gosto de saber quando algo dá errado. E parece que a alegação de dizer que um aplicativo nunca teria erros.

Então qual é a definição atual de "código robusto"?

    
por Lotus Notes 27.08.2011 / 00:17
fonte

10 respostas

33

what is the point of checking a third state when a third state is logically impossible?

Que tal um Boolean? que permite um estado NULL que não é nem verdadeiro nem falso? Agora, o que o software deve fazer? Alguns softwares precisam ser altamente resistentes a impactos, como os marcapassos. Já viu alguém adicionar uma coluna a um banco de dados que era Boolean e inicializar os dados atuais para NULL inicialmente? Eu sei que já vi isso.

Aqui estão alguns links que discutem o que significa ser robusto em termos de software:

Se você acha que existe uma definição universalmente acordada de "robusta" aqui, boa sorte. Pode haver alguns sinônimos como à prova de bomba ou à prova de idiotas. O Programador de Fita Adesiva seria um exemplo de alguém que geralmente escreve código robusto pelo menos no meu entendimento dos termos.

    
por 27.08.2011 / 00:33
fonte
11

Por causa da minha discussão, um Bool pode ter dois estados, Verdadeiro ou Falso. Qualquer outra coisa é não-conformidade com a especificação de linguagem de programação. Se a sua cadeia de ferramentas não estiver em conformidade com a sua especificação, você é lavado, não importa o que você faça. Se um desenvolvedor criasse um tipo de Bool com mais de dois estados, seria a última coisa que ele faria na minha base de código.

Opção A

if (var == true) {
    ...
} else if (var == false) {
    ...
} else {
    ...
}

Opção B

if (var == true) {
    ...
} else {
    ...
}

Eu afirmo que a opção B é mais robusta .....

Qualquer twit pode dizer-lhe para lidar com erros inesperados. Eles são geralmente fáceis de detectar quando você pensa neles. O exemplo que seu professor deu não é algo que poderia acontecer, então é um exemplo muito ruim.

A é impossível testar sem os chicotes de teste complicados. Se você não pode criá-lo, como você vai testá-lo? Se você não testou o código, como você sabe que funciona? Se você não sabe que funciona, então você não está escrevendo software robusto. Eu acho que eles ainda chamam isso de Catch22 (ótimo filme, assista algum dia).

A opção B é trivial para testar.

Próximo problema, pergunte ao professor esta pergunta: "O que você quer que eu faça sobre isso se um booleano não é verdadeiro nem falso?" Isso deve levar a uma discussão muito interessante .....

Na maioria dos casos, um core dump é apropriado, na pior das hipóteses, incomoda o usuário ou custa muito dinheiro. E se, digamos, o módulo for o sistema de cálculo de reentrada em tempo real do ônibus espacial? Qualquer resposta, não importa quão imprecisa, não pode ser pior do que abortar, o que matará os usuários. Então, o que fazer, se você sabe que a resposta pode estar errada, vá para o 50/50, ou aborte e vá para a falha de 100%. Se eu fosse um membro da equipe, eu pegaria o 50/50.

Opção A me mata A opção B me dá uma chance de sobrevivência.

Mas espere - é uma simulação da reentrada do ônibus espacial - e depois? Abortar para que você saiba sobre isso. Parece uma boa ideia? - NÃO - porque você precisa testar com o código que planeja enviar.

A opção A é melhor para simulações, mas não pode ser implantada. É inútil A opção B é o código implantado para que a simulação tenha o mesmo desempenho dos sistemas ativos.

Digamos que essa seja uma preocupação válida. A melhor solução seria isolar o tratamento de erros da lógica do aplicativo.

if (var != true || var != false) {
    errorReport("Hell just froze over, var must be true or false")
}
......
if (var == true){
 .... 
} else {
 .... 
}

Futher reading - Máquina de raios X Therac-25, Ariane 5 Rocket failure e outras (Link tem muitos links quebrados, mas informações suficientes que o Google vai ajudar)

    
por 27.08.2011 / 10:34
fonte
9

Na verdade, seu código não é mais robusto, mas é menos robusto. O% final else é simplesmente código morto que você não pode testar.

Em softwares críticos, como em espaçonaves, código morto e código geralmente não testado são proibidos: Se um raio cósmico produz um único evento perturbado que, por sua vez, faz com que seu código morto seja ativado, tudo é possível. Se o SEU ativar uma parte do código robusto, o comportamento (inesperado) permanece sob controle.

    
por 27.08.2011 / 14:11
fonte
7

Eu acho que o professor pode estar confundindo "erro" e "bug". O código robusto certamente deve ter poucos / nenhum bugs. Um código robusto pode, e em um ambiente hostil, ter um bom gerenciamento de erros (seja manipulação de exceções ou testes rigorosos de status de retorno).

Concordo que o exemplo de código do professor é bobo, mas não tão bobo quanto o meu.

// Assign 3 to x
var x = 3;
x = 3;   // again, just for sure
while (x < 3 or x > 3) { x = 3; }  // being robust
if (x != 3) { ... }  // this got to be an error!
    
por 27.08.2011 / 03:00
fonte
5

Não há acordo sobre a definição do Robust Code , pois muitas coisas na programação são mais ou menos subjetivas ...

O exemplo que seu professor dá depende da linguagem:

  • No Haskell, um Boolean pode ser True ou False , não há terceira opção
  • Em C ++, um bool pode ser true , false ou (infelizmente) vem de algum elenco duvidoso que o colocou em um caso desconhecido ... Isso não deve acontecer , mas pode, como resultado de um erro anterior.
No entanto, o que o seu professor está aconselhando obscurece o código introduzindo lógica estranha para eventos que não acontecem no meio do programa principal, então, em vez disso, eu vou apontar para Programação Defensiva .

No caso da universidade, você pode até aumentá-lo adotando uma estratégia de Design By Contract:

  • Estabeleça invariantes para classes (por exemplo, size é o número de itens na lista data )
  • Estabelecer pré-condições e pós-condições para cada função (por exemplo, essa função só pode ser invocada com a sendo menor que 10 )
  • Teste cada um deles nos pontos de entrada e saída de cada uma das suas funções

Exemplo:

class List:
  def __init__(self, items):
    self.__size = len(items)
    self.__data = items

  def __invariant(self):
    assert self.__size == len(self.__data)

  def size(self):
    self.__invariant()

    return self.__size

  def at(self, index):
    """index should be in [0,size)"""
    self.__invariant()
    assert index >= 0 and index < self.__size

    return self.__data[index]

  def pushback(self, item):
    """the subsequent list is one item longer
       the item can be retrieved by self.at(self.size()-1)"""
    self.__invariant()

    self.__data.append(item)
    self.__size += 1

    self.__invariant()
    assert self.at(self.size()-1) == item
    
por 27.08.2011 / 14:28
fonte
2

A abordagem do seu professor é totalmente equivocada.

Uma função, ou apenas um pouco de código, deve ter uma especificação que diga o que ela faz, que deve cobrir todas as entradas possíveis. E o código deve ser escrito de modo que seu comportamento seja garantido para corresponder à especificação. No exemplo, eu escreveria a especificação bem simples assim:

Spec: If var is false then the function does "this", otherwise it does "that". 

Então você escreve a função:

if (var == false) dothis; else dothat; 

e o código atende à especificação. Então o seu professor diz: E se var == 42? Olhe para a especificação: diz que a função deve fazer "that". Veja o código: A função faz "that". A função atende a especificação.

Quando o código do seu professor torna as coisas totalmente desconfiguradas é o fato de que, com sua abordagem, quando var não é nem verdadeiro nem falso, ele executará um código que nunca foi chamado antes e que não foi testado completamente, com resultados totalmente imprevisíveis.

    
por 16.05.2015 / 21:45
fonte
1

Concordo com a declaração do @gnasher729: A abordagem do seu professor é totalmente equivocada.

Robusto significa que é resistente a quebras / falhas, porque faz poucas suposições e é dissociado: é auto-contido, autodefinível e portátil. Também inclui ser adaptável a mudanças nos requisitos. Em uma palavra, seu código é durável .

Isso geralmente se traduz em funções curtas que obtêm seus dados de parâmetros passados pelo chamador e o uso de interfaces públicas para consumidores - métodos abstratos, wrappers, indiretos, interfaces de estilo COM etc. - em vez de funções que contêm código de implementação concreto .

    
por 18.09.2011 / 22:35
fonte
0

O código robusto é simplesmente um código que lida bem com falhas. Não mais nem menos.

De falhas, existem muitos tipos: código incorreto, código incompleto, valores inesperados, estados inesperados, exceções, esgotamento de recursos, .... O código robusto trata bem disso.

    
por 27.08.2011 / 15:27
fonte
0

Eu consideraria o código que você deu como um exemplo de programação defensiva (pelo menos enquanto eu uso o termo). Parte da programação defensiva é fazer escolhas que minimizem suposições feitas sobre o comportamento do resto do sistema. Por exemplo, qual deles é melhor:

for (int i = 0; i != sequence.length(); ++i) {
    // do something with sequence[i]
}

Ou:

for (int i = 0; i < sequence.length(); ++i) {
    // do something with sequence[i]
}

(Caso você tenha problemas para ver a diferença, verifique o teste de loop: o primeiro usa != , o segundo usa < ).

Agora, na maioria das circunstâncias, os dois loops se comportarão exatamente da mesma maneira. No entanto, o primeiro (comparando com != ) supõe que i será incrementado apenas uma vez por iteração. Se ele ignorar o valor sequence.length() , o loop poderá continuar além dos limites da sequência e causar um erro.

Você pode, portanto, argumentar que a segunda implementação é mais robusta: ela não depende de suposições sobre se o corpo do loop muda i (nota: na verdade, ainda faz a suposição de que i nunca é negativo). / p>

Para dar alguma motivação para o motivo pelo qual você pode não querer fazer essa suposição, imagine que o loop está digitalizando uma string, fazendo algum processamento de texto. Você escreve o loop e está tudo bem. Agora seus requisitos mudam e você decide que precisa suportar caracteres de escape na sequência de texto, portanto, você altera o corpo do loop de forma que, se detectar um caractere de escape (digamos, barra invertida), ele incrementa i para ignorar o caractere imediatamente após o caractere escapar. Agora o primeiro loop tem um bug porque se o último caractere do texto for barra invertida, o corpo do loop irá incrementar i e o loop continuará além do final da sequência.

    
por 27.08.2011 / 20:58
fonte
-1

Pessoalmente, descrevo um código como "robusto", que possui esse atributo importante:

  1. Se minha mãe se senta na frente dela e trabalha com ela, ela não pode quebrar o sistema

Agora, por pausa, quero dizer, colocar o sistema em um estado instável ou causar uma exceção UNHANDLED . Você sabe, às vezes, por um conceito simples, você pode fazer uma definição e uma explicação complexas. Mas prefiro definições simples. Os usuários são muito bons em encontrar aplicativos robustos. Se o usuário do seu aplicativo enviar várias solicitações sobre erros, sobre perda de estado, sobre fluxos de trabalho não intuitivos, etc., então há algo errado com sua programação.

    
por 27.08.2011 / 12:54
fonte