Come posso essere certo che il mio codice sia impeccabile? [duplicare]

2

Ho appena completato un esercizio dal mio libro di testo che voleva che scrissi un programma per verificare se un numero è primo o meno.

L'ho provato e sembra funzionare bene, ma come posso essere sicuro che funzionerà per ogni numero primo?

public boolean isPrime(int n)
    {
       int divisor = 2;
       int limit = n-1 ; 

            if (n == 2)
            {                  
                return true;
            }
            else
            {
                int mod = 0;
                while (divisor <= limit)
                {
                      mod = n % divisor;
                      if (mod == 0)
                      {
                          return false;
                      }
                    divisor++;
                }

                if (mod > 0)
                {
                    return true;
                }
            }

      return false;
}

Si noti che questa domanda non è un duplicato di Programmi Teoricamente privi di errori perché la domanda si interroga su se si possono scrivere programmi privi di bug di fronte ai risultati limitativi come la prova di Turing dell'incomputabilità dell'arresto, il teorema di Rice e i teoremi di incompletezza di Godel. Questa domanda chiede a in che modo un programma può essere mostrato privo di errori.

    
posta David 02.06.2014 - 15:50
fonte

3 risposte

17

Non puoi stabilire la correttezza tramite test. Tutti i test dicono che il sistema risponde correttamente ai casi che hai esercitato . (Anche se esiste un numero finito di possibili input, non è sufficiente, perché in senso stretto, il test ti dice solo che il sistema ha risposto correttamente in questa istanza - potrebbe essere segretamente non deterministico, e non lo sapresti mai.)

Per stabilire che il tuo codice è effettivamente corretto per tutti i possibili input, è richiesta verifica . Ciò equivale a una prova matematica che la trasformazione che il tuo codice rappresenta è effettivamente quella corretta per il problema specificato.

La verifica è perfettamente possibile, in particolare per le attività matematiche, ma di solito non vale lo sforzo aggiunto. In particolare, assicurandosi che il vostro requisito indichi effettivamente ciò che sembra affermare e il vostro codice definisca effettivamente ciò che sembra definire è sorprendentemente difficile e peloso quando tutti i dettagli di un linguaggio di programmazione, una piattaforma, una libreria standard, ecc. Vengono considerati correttamente. Questo è il motivo per cui la verifica non è riscontrabile in pratica tanto quanto un numero elevato di test di regressione.

    
risposta data 02.06.2014 - 15:56
fonte
3

Esistono numerosi framework per dimostrare che il codice è corretto. Puoi leggere alcuni di essi in libri come

Per il tuo programma, presumo che non ti importi del n negativo e che puoi gestire i casi n==0 , n==1 e n==2 senza alcun aiuto; Vedrò solo il caso in cui n>2 .

La chiave è di notare che (nei casi in cui n > 2 ) ogni volta che viene valutata l'espressione di guardia del ciclo divisor <= limit , i seguenti 5 fatti sono veri dello stato del programma:

n % i != 0, for all i from 2 up to, but not including divisor
limit == n-1
2 <= divisor && divisor <= n
mod != 0 || divisor == 2
n is the same as the argument  // i.e. n hasn't been changed

Questi 5 fatti sono chiamati invarianti del ciclo. Confidare che l'invarianza del loop tenga dove ho detto che lo fa, puoi controllare

  • che contiene la prima volta che la guardia viene valutata e
  • che, se l'invariante è vero e la guardia è vera, allora una esecuzione del corpo del ciclo lascerà il programma in uno stato in cui l'invariante è di nuovo vero.

Pertanto, quando e se il ciclo termina normalmente *, tutto quanto sopra è vero e così è divisor > limit . Questo ti dice che divisor == n e quindi hai

n % i != 0, for all i from 2 up to, but not including n
mod != 0
n is the same as the argument // i.e. n hasn't been changed

Pertanto, se il ciclo termina normalmente, verrà eseguito il return true dopo il ciclo e l'argomento è primo.

Gli unici casi di cui preoccuparsi sono quelli in cui il ciclo non termina normalmente. Ci sono due motivi per cui un ciclo potrebbe non terminare normalmente: è infinito o c'è un salto fuori da esso, come un'interruzione, un ritorno o un'eccezione. Nel tuo codice, è possibile mostrare che il ciclo non è infinito e l'unico salto è quando n % divisor == 0 . Dal momento che, a quel punto, abbiamo anche

2 <= divisor       // from the invariant
divisor <= limit   // from the guard
limit == n-1       // from the invariant

abbiamo 2<=divisor && divisor < n . Quindi se viene eseguito il return false nel ciclo, è solo quando n % divisor == 0 e divisor è maggiore di 1 e minore di n , e quindi n (e quindi l'argomento) non è Prime.

In sintesi

  • per non scadenti (maggiore di 2), il ciclo non può terminare normalmente e quindi è necessario eseguire return false e
  • per i primi (maggiore di 2) il ciclo deve terminare normalmente e in uno stato in cui mod != 0 e quindi verrà eseguito return true .

La dimostrazione suggerisce alcuni modi per semplificare il codice. I libri che ho menzionato forniscono dei modi per rendere l'analisi più formale, anche se penso che solo Hehner's offra un modo per gestire le prime uscite dai loop.

[*] Con "termina normalmente", intendo che termina perché la sua guardia è falsa.

Questa analisi ti dà la certezza assoluta che il tuo codice è impeccabile? Spero di no. Ma dovrebbe darti rassicurazione. Uno può utilizzare strumenti come ESC / Java2 o Spec # in modo che un computer possa verificare che non si siano commessi errori. Ciò aumenterebbe il livello di fiducia che il codice ha raggiunto un livello molto alto. Ovviamente potrebbero esserci dei bug nel programma che si usa per controllare la verifica o nell'hardware su cui gira. Per scopi pratici, codice come il tuo può essere verificato e la verifica può essere verificata con strumenti accuratamente scritti e affidabili.

    
risposta data 02.06.2014 - 18:38
fonte
1

Per qualcosa come numeri primi, non sarai mai sicuro che funzionerà per tutti i numeri primi, perché l'insieme di tutti i numeri primi è infinitamente lungo, e quindi non smetterei mai di codificare il tuo test unitario. Detto questo, ci sono molti modi diversi per giudicare la qualità del codice e passare un test unitario è solo uno di questi. Ad esempio, se stai costruendo un pezzo di codice per trovare tutti i numeri primi nel mondo, la soluzione probabilmente funzionerà correttamente per i numeri più bassi, ma man mano che la dimensione dei numeri aumenta, supererai la dimensione di int , il che significa che hai una piccola limitazione nel tuo programma. Inoltre, le prestazioni si ridurranno man mano che i numeri aumentano e il ciclo richiede più tempo.

Un altro piccolo punto, i numeri negativi non possono essere primi, quindi invece di int, potresti usare int unsigned, o unsigned long per estendere l'intervallo di numeri che puoi verificare.

Nel tuo caso però, quello che stai facendo è concentrarsi sulla cosa sbagliata. Nessun codice è mai perfetto. Ci saranno sempre bug. Soddisfa i requisiti che hai impostato prima di te? Funziona adeguatamente bene per le tue esigenze?

Allora stai bene.

    
risposta data 02.06.2014 - 15:58
fonte

Leggi altre domande sui tag