È così che il linguaggio dinamico affronta i requisiti dinamici? [chiuso]

3

La domanda è nel titolo. Voglio che il mio modo di pensare sia verificato da persone esperte. Puoi aggiungere altro o ignorare la mia opinione, ma dammi una ragione.

Ecco un esempio di esempio: Supponiamo che sia necessario implementare un gioco di combattimento. Inizialmente, il gioco include solo i combattenti, che possono attaccarsi a vicenda. Ogni combattente può pugni, calci o blocchi di attacchi in arrivo. I combattenti possono avere vari stili di combattimento: Karate, Judo, Kung Fu ... Questo è tutto per il semplice universo del gioco. In un OO come Java, può essere implementato in questo modo:

abstract class Fighter {
    int hp, attack;

    void punch(Fighter otherFighter);
    void kick(Fighter otherFighter);
    void block(Figther otherFighter); 
};

class KarateFighter extends Fighter { //...implementation...};
class JudoFighter extends Fighter { //...implementation... };
class KungFuFighter extends Fighter { //...implementation ... };

Questo va bene se il gioco rimane così per sempre. Ma, in qualche modo, i game designer decidono di cambiare il tema del gioco: invece di un semplice gioco di combattimento, il gioco si evolve fino a diventare un GdR, in cui i personaggi non possono solo combattere ma svolgere altre attività, ad esempio il personaggio può essere un prete, un ragioniere, uno scienziato ecc ... A questo punto, per renderlo più generico, dobbiamo cambiare la struttura del nostro progetto originale: Fighter non è più usato per riferirsi a una persona; si riferisce a una professione. Le classi specializzate di Fighter (KaraterFighter, JudoFighter, KungFuFighter). Ora dobbiamo creare una classe generica denominata Person . Tuttavia, per adattare questa modifica, devo cambiare le firme dei metodi delle operazioni originali:

class Person {
    int hp, attack;
    List<Profession> skillSet;
};

abstract class Profession {};

class Fighter extends Profession {         
    void punch(Person otherFighter);
    void kick(Person otherFighter);
    void block(Person otherFighter); 
};

class KarateFighter extends Fighter { //...implementation...};
class JudoFighter extends Fighter { //...implementation... };
class KungFuFighter extends Fighter { //...implementation ... };

class Accountant extends Profession {
     void calculateTax(Person p) { //...implementation...};
     void calculateTax(Company c) { //...implementation...};
};
//... more professions...

Ecco i problemi:

  1. Per adattarsi alle modifiche al metodo, devo correggere i luoghi in cui i metodi modificati sono chiamati (refactoring).

  2. Ogni volta che viene introdotto un nuovo requisito, è necessario interrompere l'attuale progettazione strutturale per adattare le modifiche. Questo porta al primo problema.

  3. La struttura rigida rende difficile il riutilizzo del codice. Una funzione può accettare solo i tipi predefiniti, ma non può accettare tipi sconosciuti futuri. Una funzione scritta è legata al suo universo attuale e non ha modo di adattarsi ai nuovi tipi, senza modifiche o riscrittura da zero. Vedo che Java ha molti metodi deprecati.

OO è un caso estremo perché ha ereditarietà per sommare la complessità, ma in generale per il linguaggio tipizzato staticamente, i tipi sono molto severi.

Al contrario, un linguaggio dinamico può gestire il caso precedente come segue:

;;fighter1 punch fighter2
(defun perform-punch (fighter1 fighter2) ...implementation... )

;;fighter1 kick fighter2
(defun perform-kick (fighter1 fighter2) ...implementation... )

;;fighter1 blocks attacks from fighter2
(defun perform-block (fighter1 fighter2) ...implementation... )

fighter1 e fighter2 possono essere qualsiasi cosa purché abbia i dati richiesti per il calcolo; o metodi (digitazione anatra). Non devi cambiare dal tipo Fighter a Person . Nel caso di Lisp, poiché Lisp ha una sola struttura di dati: lista, è ancora più facile adattarsi alle modifiche. Tuttavia, altri linguaggi dinamici possono avere anche comportamenti simili.

Io lavoro principalmente con linguaggi statici (principalmente C e Java, ma lavorare con Java era molto tempo fa). Ho iniziato a imparare Lisp e altre lingue dinamiche quest'anno. Posso vedere come aiuta a migliorare la mia produttività.

    
posta Amumu 11.12.2012 - 08:46
fonte

5 risposte

2

Non sono del tutto sicuro di quale sia la tua domanda, perché la domanda include una serie di termini vaghi: "linguaggio dinamico", "questo" e "requisito dinamico".

Il "linguaggio dinamico" può riferirsi a qualsiasi linguaggio di programmazione che faccia qualcosa che altre lingue possano fare in fase di compilazione. Ciò può includere aspetti dei valori dei dati (nulli o non nulli), aspetti del sistema di tipi (ad es. Creazione di nuovi tipi in fase di esecuzione), aspetti del metodo e invio delle funzioni e molti altri aspetti. La tua domanda sembra interessarsi principalmente di invio di metodi dinamici (sebbene tu parli di tipizzazione statica in seguito). La mia attitudine a questo sarebbe che linguaggi diversi forniscano uno con strumenti diversi su come gestire i requisiti e anche con requisiti in evoluzione. In questo senso, la risposta alla tua domanda è probabilmente "sì". Laddove linguaggi come Java o Scala forniscono interfacce e tratti per rendere più semplice l'adattamento del design dei programmi alle mutevoli esigenze, un linguaggio come Clojure fornisce dispatch dinamico, Groovy fornisce metaprogrammazione e ancora altri linguaggi non forniscono affatto o pochissimi strumenti. Penso che quale di questi strumenti sia più adatto per l'attività dipende dai dettagli del compito, i dettagli della tua definizione di "migliore" (tempo e sforzo di sviluppo, disponibilità di programmatore, prestazioni del codice, probabilità di certe classi di errori durante runtime ecc.) e su preferenze personali e non si può avere una risposta in generale.

    
risposta data 31.12.2012 - 17:12
fonte
1

Penso che la risposta si riassuma in: sì, un linguaggio "dinamico" aiuta la produttività della codifica in quanto è possibile apportare rapidamente e facilmente cambiamenti radicali. Tuttavia, danneggia la tua produttività di rilascio poiché hai creato un pasticcio di codice che non ha una struttura coerente e molti errori rilevabili solo in fase di esecuzione che dovranno essere risolti in un secondo momento, piuttosto che prima di entrare in una "statica" lingua.

Semplicemente non esiste un sostituto per fare le cose correttamente, e ciò significa pensiero, progettazione e considerazione attenta dell'impatto delle tue modifiche.

    
risposta data 28.01.2014 - 14:39
fonte
1

Una lingua dinamica potrebbe lamentarsi di meno quando si effettua il refactoring, ma potrebbe anche nascondere gli errori di tipo da te che si otterrebbero in fase di compilazione in una lingua tipizzata in modo statico. In generale, dovrai refactoring il tuo codice quando le tue esigenze cambiano indipendentemente dal fatto che tu sia in una lingua dinamica o tipizzata staticamente. Non c'è nessun proiettile d'argento lì.

C e Java non sono buoni punti di riferimento per i linguaggi statici, perché hanno sistemi di tipi e funzionalità molto limitati. Non puoi ottenere nulla in Java senza estrema verbosità. Vuoi passare una tupla? Beh, non puoi. Records? Non puoi Vuoi una funzione anonima? Dovrai scrivere 4 righe di testo per un payload che potrebbe essere il più breve di 2 + 2. Dio ti aiuta se vuoi curry o comporre funzioni.

Devi ricorrere a un gruppo di hack di OO per aggirare quelle caratteristiche mancanti, e anche allora puoi comunque spararti ai piedi con ereditarietà e null perché molte persone non capiscono quanto siano pericolosi. Quindi, se questo è il tuo unico punto di riferimento, praticamente qualsiasi linguaggio dinamico ti consentirà di scrivere meno codice, ma non necessariamente in virtù della loro digitazione dinamica. Lingue come Standard ML o F # forniscono anche queste caratteristiche senza appesantirti con annotazioni di tipo perché il compilatore può dedurre i tipi.

Un buon linguaggio tipizzato staticamente è più espressivo di un linguaggio tipizzato dinamicamente con funzionalità equivalenti. La digitazione dinamica ti priva della capacità di esprimere vincoli sui tuoi dati usando il sistema dei tipi. Questo perché la digitazione dinamica è un caso speciale di digitazione statica: è più corretto pensare a questi linguaggi come unityped . Se ti sembra strano, immagina la codifica in Java usando solo le variabili e i cast di Object. Il problema è che le lingue tradizionali tipizzate staticamente sono storpiate. Ma se stai codificando in Lisp, non penso che tu sia troppo interessato a ciò che è mainstream.

    
risposta data 28.01.2014 - 19:31
fonte
0

Un linguaggio dinamico può essere grande di fronte a requisiti dinamici. Personalmente, preferisco Python a C quando mi trovo di fronte a un sacco di cambiamenti. C'è molto meno cerimonia rispetto a C ++, C #, Java. Ma ...

Indipendentemente dal linguaggio, l'architettura del sistema / progetto ha un impatto enorme sul modo in cui possiamo adattarci ai cambiamenti, come quelli che menzioni. Per me la domanda più grande è "quanto velocemente possiamo refactoring per accogliere i cambiamenti". Per il tuo esempio, le classi base avrebbero bisogno di modifiche e preferirei avere ereditarietà multipla.

Nei nostri progetti, i documenti di analisi e di progettazione esterni vengono rivisitati in modo massivo quando si verificano grandi cambiamenti. Preferiamo pubblicare documenti revisionati e da qui fare una grande lista di richieste di modifica. E sì, i test suite hanno bisogno di molto lavoro extra.

    
risposta data 28.01.2014 - 13:01
fonte
0

Non ne sono sicuro, qui sto ottenendo il tuo punto, ma diversi tipi di linguaggi in realtà non risolvono il problema di fondo che è, fondamentalmente, che una base di codice scritta una volta deve essere espansa. Mentre questo è qualcosa che il refactoring, ovviamente, può risolvere, forse il concetto / design generale del codice è stato sfortunato in primo luogo.

Per esempio, che ne dici di un Fighter (o una Persona se in seguito lo cambi) fa "solo qualcosa". Usando, ad esempio, i generici Java, puoi anche limitare ciò che possono fare.

class Fighter {
    void doSomething(Command<? extends FighterCommand> cmd) {
        cmd.execute();
    }
}

Ora puoi facilmente, in qualsiasi momento durante lo sviluppo, espandere le abilità di un combattente aggiungendo semplicemente un altro comando.

class KickCommand extends FighterCommand {
    @Override
    public void execute() {
        kickSomeone();
    }
}

L'uso del schema di comando qui ti assolverà dal cambiare le interfacce in tutto il luogo solo perché il combattente deve colpire qualcuno anche adesso.

E avvicinarsi a ciò che stavi chiedendo, estendere il concetto di Combattente a un concetto di Persona dovrebbe essere possibile facendo estendere a Fighter (la nuova ora) Persona. Se una persona può fare Qualcosa (), anche questa è solo una @ Override nel Combattente lontano (o la cancellazione del metodo Fighter:)).

    
risposta data 28.01.2014 - 17:50
fonte

Leggi altre domande sui tag