Possiamo ridurre la confusione negli strumenti diff basati sulla linea annotando il codice con alcuni token unici?

4

Diciamo che ho

procedure1() {
    --body of first procedure--
}

Quindi lo rinominerò in procedure2 e creerò un procedure1 sopra di esso:

procedure1() {
    --body of second procedure--
}

procedure2() {
    --body of first procedure--
}

Non una volta uno strumento di diffusione basato sulla linea ha evidenziato il codice da --body of second procedure fino a procedure2() { come nuovo codice all'interno di procedure1 .

Questo è destinato a succedere, dal momento che molti strumenti di diff sono ignari della struttura sottostante del codice sorgente. Anche gli strumenti di diffusione basati su AST non possono funzionare molto bene, a causa di diversi motivi e so che le persone vogliono davvero uno strumento di diffusione semantico , ma non succederà.

Non ho visto una discussione, tuttavia, sul fatto che sarebbe pratico annotare il codice in modo tale che uno strumento di diffusione basato sulla linea comprendesse la struttura sottostante del codice sorgente.

Ad esempio, potrei inserire alcuni UUID nel codice, come questo:

//BeginBlock{E999A3BF-626E-428F-A2C1-6AFF0CD22BF2}
procedure1() {
    --body of first procedure--
}
//EndBlock

E il codice modificato sarebbe simile a questo:

//BeginBlock{7C734F0A-92F4-45EB-B653-DBB9A0F18354}
procedure1() {
    --body of second procedure--
}
//EndBlock

//BeginBlock{E999A3BF-626E-428F-A2C1-6AFF0CD22BF2}
procedure2() {
    --body of first procedure--
}
//EndBlock

Il punto è assegnare alcuni token (univoci all'interno di un file o di un progetto) ad alcuni segni che riflettono parte della struttura del codice sorgente.

Un IDE può aggiornare automaticamente queste annotazioni e potrebbe aiutare uno strumento diff a rilevare meglio i cambiamenti strutturali. Lo strumento eseguirà la scansione del codice una sola volta per identificare le sezioni del programma (e il modo in cui sono state spostate) e quindi confrontare i blocchi con lo stesso ID.

Pensi che questo approccio sia pratico?

    
posta Jay Lorn 06.07.2013 - 11:31
fonte

3 risposte

5

L'algoritmo Patience Diff è progettato per risolvere questo problema, nella misura in cui è possibile farlo con testo non annotato. Da quell'articolo:

[Patience Diff] only considers lines that are (a) common to both files, and (b) appear only once in each file. This means that most lines containing a single brace or a new line are ignored, but distinctive lines like a function declaration are retained. Computing the longest common subsequence of the unique elements of both documents leads to a skeleton of common points that almost definitely correspond to each other. The algorithm then sweeps up all contiguous blocks of common lines found in this way, and recurses on those parts that were left out, in the hopes that in this smaller context, some of the lines that were ignored earlier for being non-unique are found to be unique. Once this process is finished, we are left with a common subsequence that more closely corresponds to what humans would identify.

È disponibile in Git (dalla versione 1.8.2) utilizzando un flag della riga di comando:

git diff --patience

O un'opzione di configurazione:

git config diff.algorithm patience

Questo algoritmo continuerà a soffrire quando le linee univoche non offrono sufficienti informazioni per ricostruire la cronologia. Nel tuo esempio:

I rename it into procedure2 and create a procedure1 above it

Il fatto che tu dica e sta già andando a confondere qualsiasi algoritmo di diff, perché stai facendo più cambiamenti semantici in una patch. Il numero di modifiche apportate è il numero di modifiche che un algoritmo diff ha bisogno di inferire che hai creato.

Il tagging di blocchi lessicali con metadati aggiuntivi non è una soluzione reale. Si potrebbe anche ottenere un controllo delle versioni perfetto aggiungendo un ID per ogni carattere del documento: è un miglioramento, ma non scala. Preferisco di gran lunga vedere un editor che nasconde, commette e disimpegna automaticamente attorno a un refactoring automatizzato come un rinominare. Quindi, almeno, so che i cambiamenti sono isolati semanticamente, e un algoritmo diff post facto avrà un tempo più semplice.

    
risposta data 05.09.2013 - 06:50
fonte
1

Bene, in primo luogo potrei obiettare che la creazione di una nuova procedura1 che fa ciò che procedure2 ha fatto, con la stessa firma, è una supplica per i problemi.

Anche se procedure1 fosse il nome più naturale per la nuova procedura, dovresti differenziarlo a livello di nome, per evitare confusione lungo la linea.

In caso di modifica di questa importazione, il nome procedure1 dovrebbe non esistere più , quindi "informa" tutte le fonti dipendenti che qualcosa è cambiato a cui devono reagire.

Quindi, hai già qualcosa che può assumere il ruolo di un UUID - commenti della documentazione della funzione.

Ho provato a modificare un semplice file con rudimentali commenti pseudo-Javadoc nel modo in cui hai indicato.

Lo strumento diff in modalità linea indica correttamente che è stata aggiunta una nuova procedura e che quello vecchio ha cambiato nome.

Certo, la parentesi finale di chiusura è stata rappresentata in modo errato e questo potrebbe essere evitato aggiungendo un numero seriale o un timbro data alla parentesi graffa (cioè } // 20130705 ), ma questo sembra più un problema che vale la pena.

/**                                                             /**
 * Procedure 1, doing things.                                    * Procedure 1, doing things.
 *                                                               *
 */                                                              */

procedure2() {                                                | procedure1() {
        // Do operation 1                                               // Do operation 1
        // Do operation 2                                               // Do operation 2
}                                                             <
                                                              <
/**                                                           <
 * Procedure 2, the new one.                                  <
 *                                                            <
 */                                                           <
                                                              <
procedure1() {                                                <
        // Do operation 3                                     <
        // Do operation 4                                     <
}                                                               }

procedureFinal() {                                              procedureFinal() {
        // Finalize.                                                    // Finalize.
}                                                               }
    
risposta data 07.07.2013 - 00:15
fonte
0

Se si dipende dall'ordine e dalla struttura del file di testo che contiene l'origine del codice, si può aiutare a gestire i set di modifiche, non lo si otterrà mai abbastanza bene. Uno strumento di differenziamento migliore potrebbe aiutarti, alcuni.

Alcune idee.

  1. Cambiando radicalmente l'intento e i nomi delle funzioni, stai essenzialmente chiedendo che l'intero sottoinsieme di sorgenti, almeno in questo file, venga rivisto per coerenza, errori logici, integrazione, boo, ecc. non può cambiare l'intento del codice e aspettarsi uno strumento di diffusione automatica basato su caratteri o linee per provare a dedurre le modifiche equivalenti nel significato .

  2. Se trovi che questo sta accadendo molto, hai due opzioni come vedo. È possibile fare meno affidamento sugli strumenti di diffusione automatici e una volta che un file viene modificato oltre la comparabilità, è sufficiente rivederlo come nuovo. Leggi le funzioni. Capire se funzionano. È noioso. Ma hai cambiato il significato. Quindi non ci sono scorciatoie per garantire la correttezza. Quindi .... inoltre, se vuoi avere più QA automatici, devi dividere queste funzioni in due file , in base alla loro semantica. La prossima volta cambi le cose. A volte sono un sacco di file, ma quello che fai è definire in modo più chiaro la struttura testuale o di archiviazione, ciò che ogni cosa fa invece di basarsi sull'unicità dei nomi di funzioni o posizionamento relativo all'interno di un singolo file da definire la struttura semantica prevista delle funzioni che compongono il tuo progetto.

Quindi dividi le cose in file.

Se la procedura2 sta svolgendo il lavoro di procedura1, si tratta di un grosso cambiamento di intenti.

L'unico modo per diffonderlo in modo pulito è in realtà programmare su un'interfaccia. E l'uso efficace di file più piccoli dovrebbe facilitare questo.

Noto anche, guarda qualcosa come il GAC manifest binding in .Net. Come tenta di abilitare i consumer della biblioteca a specificare che hanno bisogno di una versione 1.x, ma che consente anche, produttori di librerie di specificare, che questa versione 2.1 è la funzione per la funzione retrocompatibile con tutti i requisiti 1.x. Questo è un po 'quello che stai cercando con l'idea GUID. Un'interfaccia stabile.

    
risposta data 05.09.2013 - 14:36
fonte