Come vengono gestite grandi basi di codici non OO?

25

Vedo sempre che l'astrazione è una funzionalità molto utile che l'OO fornisce per gestire il codice base. Ma come vengono gestite grandi basi di codici non OO? Oppure diventano " Big Ball of Mud "?

Aggiornamento:
Sembrava che tutti pensassero che l'astrazione è solo la modularizzazione o il nascondiglio dei dati. Ma IMHO, significa anche l'uso di "Classi astratte" o "Interfacce", che è un must per l'iniezione di dipendenza e quindi di test. Come le basi di codice non OO gestiscono questo? Inoltre, oltre all'astrazione, l'incapsulamento aiuta molto a gestire basi di codice di grandi dimensioni in quanto definisce e limita la relazione tra dati e funzioni.

Con C, è molto possibile scrivere codice pseudo-OO. Non so molto di altre lingue non OO. Quindi, è il modo di gestire grandi basi di codice C?

    
posta Gulshan 26.08.2011 - 11:51
fonte

14 risposte

42

Sembra che tu pensi che l'OOP sia l'unico mezzo per raggiungere l'astrazione.

Mentre OOP è certamente molto bravo a farlo, non è affatto l'unico modo. I progetti di grandi dimensioni possono anche essere mantenuti gestibili mediante una modularizzazione senza compromessi (basta guardare Perl o Python, entrambi che si sono distinti in questo, così come i linguaggi funzionali come ML e Haskell) e utilizzando meccanismi come i modelli (in C ++). / p>     

risposta data 25.11.2010 - 12:47
fonte
10

Moduli, funzioni (esterne / interne), subroutine ...

come ha detto Konrad, OOP non è l'unico modo per gestire basi di codice di grandi dimensioni. In effetti, un sacco di software è stato scritto prima (prima di C ++ *).

    
risposta data 25.11.2010 - 13:17
fonte
7

Il principio di modularità non è limitato ai linguaggi orientati agli oggetti.

    
risposta data 29.11.2010 - 20:03
fonte
5

Realisticamente o cambiamenti poco frequenti (si pensi ai calcoli di pensionamento della Social Security) e / o una conoscenza profondamente radicata perché le persone che mantengono tale sistema lo hanno fatto per un po '(il cinico è la sicurezza del lavoro).

Le soluzioni migliori sono la convalida ripetibile, con cui intendo test automatizzato (ad es. test di unità) e test umani che seguono fasi proibite (ad esempio test di regressione) "invece di fare clic e vedere quali interruzioni".

Per iniziare a passare a una sorta di test automatico con una base di codice esistente, ti consiglio di leggere Michael Feather's Working Effectively with Legacy Code , che descrive in dettaglio gli approcci per portare le codebase esistenti fino a quando non verrà implementata una sorta di framework di test ripetibile OO. Ciò porta al tipo di idee a cui altri hanno risposto con la modularizzazione, ma il libro descrive il giusto approccio per farlo senza rovinare le cose.

    
risposta data 25.11.2010 - 15:27
fonte
4

Anche se l'iniezione di dipendenza basata su interfacce o classi astratte è un modo molto bello di fare test, non è necessario. Non dimenticare che quasi tutte le lingue hanno o un puntatore a funzione o un'eval, che possono fare qualsiasi cosa tu possa fare con un'interfaccia o una classe astratta (il problema è che possono fare più , inclusi molti errori cose, e che non forniscono di per sé metadati). Un tale programma può effettivamente ottenere un'iniezione di dipendenza con questi meccanismi.

Ho trovato la necessità di essere rigoroso con i metadati per essere molto utile. Nei linguaggi OO le relazioni tra i bit di codice sono definite (in una certa misura) dalla struttura della classe, in un modo abbastanza standardizzato per avere cose come una reflection API. Nei linguaggi procedurali può essere utile inventare te stesso.

Ho anche scoperto che la generazione del codice è molto più utile in un linguaggio procedurale (rispetto a un linguaggio orientato agli oggetti). Questo garantisce che i meta-dati siano sincronizzati con il codice (dato che è usato per generarlo) e ti danno qualcosa di simile ai punti di taglio della programmazione orientata agli aspetti: un posto dove puoi iniettare il codice quando ne hai bisogno. A volte è l'unico modo per programmare in modo DRY in un ambiente del genere che riesco a capire.

    
risposta data 26.08.2011 - 01:01
fonte
3

In realtà, come hai scoperto di recente , le funzioni di primo ordine sono tutto ciò che serve per l'inversione di dipendenza.

C supporta le funzioni di primo ordine e anche le chiusure in una certa misura . E i macro C sono una potente funzionalità per la programmazione generica, se gestita con le dovute cure.

È tutto lì. SGLIB è un buon esempio di come C può essere usato per scrivere codice altamente riusabile. E credo che ci sia molto di più là fuori.

    
risposta data 23.05.2017 - 14:40
fonte
2

Anche senza astrazione molti programmi sono suddivisi in sezioni di qualche tipo. Queste sezioni di solito riguardano attività o attività specifiche e lavorate su quelle allo stesso modo in cui lavorereste sui bit più specifici dei programmi astratti.

Nei progetti di piccole e medie dimensioni è più facile farlo con un'implementazione OO purista a volte.

    
risposta data 29.11.2010 - 17:56
fonte
2

L'astrazione, le classi astratte, l'iniezione delle dipendenze, l'incapsulamento, le interfacce e così via, non sono l'unico modo per controllare basi di codice di grandi dimensioni; questo è giusto e orientato agli oggetti.

Il segreto principale è evitare di pensare a OOP durante la codifica di non-OOP.

La modularità è la chiave dei linguaggi non OO. In C questo si ottiene proprio come David Thornley ha appena citato in un commento:

The interface goes in the .h file, publicly available functions in the .c file, and private variables and functions get the static access modifier attached.

    
risposta data 29.08.2011 - 08:15
fonte
1

Un modo di gestire il codice è decomporlo nei seguenti tipi di codice, lungo le linee dell'architettura MVC (model-view-controller).

  • Gestori di input: questo codice riguarda dispositivi di input come mouse, tastiera, porta di rete o astrazioni di livello superiore come eventi di sistema.
  • Gestori di output - Questo codice riguarda l'utilizzo dei dati per manipolare dispositivi esterni come monitor, luci, porte di rete, ecc.
  • Modelli - Questo codice riguarda la dichiarazione della struttura dei dati persistenti, le regole per la convalida dei dati persistenti e il salvataggio di dati persistenti su disco (o altra periferica di dati persistenti).
  • Visualizzazioni - Questo codice tratta i dati di formattazione per soddisfare i requisiti di vari metodi di visualizzazione come browser Web (HTML / CSS), GUI, riga di comando, formati di dati del protocollo di comunicazione (ad es. JSON, XML, ASN.1, ecc.) .
  • Algoritmi: questo codice trasforma ripetutamente un set di dati di input in un set di dati di output il più velocemente possibile.
  • Controllori - Questo codice prende gli input tramite i gestori di input, analizza gli input utilizzando algoritmi e quindi trasforma i dati con altri algoritmi combinando opzionalmente input con dati persistenti o semplicemente trasformando gli input, e quindi facoltativamente salvando i dati trasformati in persistente tramite il software del modello e opzionalmente la trasformazione dei dati tramite il software di visualizzazione per il rendering su un dispositivo di output.

Questo metodo di organizzazione del codice funziona bene per il software scritto in qualsiasi linguaggio OO o non OO poiché i modelli di progettazione comuni sono spesso comuni a ciascuna area. Inoltre, questi tipi di limiti di codice sono spesso i più liberamente accoppiati, ad eccezione degli algoritmi perché collegano i formati dei dati dagli input al modello e quindi agli output.

Le evoluzioni di sistema spesso prendono la forma di avere il software in grado di gestire più tipi di input o più tipi di output, ma i modelli e le visualizzazioni sono uguali ei controller si comportano in modo molto simile. Oppure un sistema può nel tempo aver bisogno di supportare sempre più diversi tipi di output anche se gli input, i modelli, gli algoritmi sono gli stessi e i controller e le visualizzazioni sono simili. Oppure un sistema può essere aumentato per aggiungere nuovi modelli e algoritmi per lo stesso insieme di input, output simili e viste simili.

La programmazione OO in un modo rende difficile l'organizzazione del codice perché alcune classi sono profondamente legate alle strutture di dati persistenti e altre no. Se le strutture dati persistenti sono intimamente collegate a cose come le relazioni 1: N in cascata o le relazioni m: n, è molto difficile decidere i limiti della classe finché non hai codificato una parte significativa e significativa del tuo sistema prima di sapere che hai capito bene . Qualsiasi classe legata alle strutture dati persistenti sarà difficile da evolvere quando lo schema dei dati persistenti cambia. Le classi che gestiscono algoritmi, formattazione e analisi sono meno esposte alle modifiche nello schema delle strutture di dati persistenti. L'utilizzo di un tipo di organizzazione del codice MVC consente di isolare meglio le modifiche al codice mendace al codice del modello.

    
risposta data 29.08.2011 - 07:32
fonte
0

Quando si lavora in lingue prive della struttura incorporata e delle funzionalità dell'organizzazione (ad esempio se non ha spazi dei nomi, pacchetti, assiemi, ecc.) o dove questi non sono sufficienti per mantenere sotto controllo una base di codice di quella dimensione, la risposta naturale è quello di sviluppare le nostre strategie per organizzare il codice.

Questa strategia organizzativa include probabilmente standard relativi a dove conservare diversi file, cose che devono accadere prima / dopo determinati tipi di operazioni, convenzioni di denominazione e altri standard di codifica, oltre a un sacco di "questo è come è impostato - non scherzare! " scrivi commenti - che sono validi purché spieghino perché!

Dato che molto probabilmente la strategia finirà per essere adattata alle esigenze specifiche del progetto (persone, tecnologie, ambiente, ecc ...) è difficile dare una soluzione valida per tutti alla gestione di grandi dimensioni basi di codice.

Pertanto ritengo che il miglior consiglio sia quello di abbracciare la strategia specifica del progetto e farne una priorità fondamentale gestirla: documentare la struttura, perché è così, i processi per apportare modifiche, controllarlo per assicurarsi che sia in corso aderito, e in modo cruciale: cambialo quando deve cambiare.

Abbiamo familiarità con le classi e i metodi di refactoring, ma con una grande base di codici in tale linguaggio è la stessa strategia organizzativa (completa di documentazione) che deve essere sottoposta a refactoring se e quando necessario.

Il ragionamento è lo stesso del refactoring: svilupperai un blocco mentale verso il lavoro su piccole parti del sistema se ritieni che l'organizzazione complessiva di esso sia un disastro, e alla fine lo permetta di deteriorarsi (almeno questo è la mia opinione su di esso).

Anche gli avvertimenti sono gli stessi: usa i test di regressione, assicurati di poter facilmente annullare se il refactoring va male, e progetta in modo da facilitare il refactoring in primo luogo (o semplicemente non lo farai!).

Sono d'accordo sul fatto che è molto più complicato del refactoring del codice diretto, ed è più difficile convalidare / nascondere il tempo da manager / clienti che potrebbero non capire perché deve essere fatto, ma questi sono anche i tipi di progetto più inclini al software rot causato da progetti di primo livello inflessibili ...

    
risposta data 26.08.2011 - 13:11
fonte
0

Se stai chiedendo informazioni sulla gestione di un grande code-base, stai chiedendo come mantenere il tuo codice base ben strutturato su un livello relativamente grossolano (librerie / moduli / costruzione di sottosistemi / utilizzo di spazi dei nomi / avere i documenti giusti a i posti giusti ecc.). I principi OO, in particolare "classi astratte" o "interfacce", sono principi per mantenere il tuo codice pulito internamente, a un livello molto dettagliato. Pertanto, le tecniche per mantenere gestibile una grande base di codice non differiscono per OO o codice non OO.

    
risposta data 29.08.2011 - 08:57
fonte
0

Il modo in cui viene gestito è scoprire i bordi degli elementi che usi. Ad esempio, i seguenti elementi in C ++ hanno un bordo chiaro e qualsiasi dipendenza al di fuori del confine deve essere attentamente pensata:

  1. funzione gratuita
  2. funzione membro
  3. class
  4. oggetto
  5. Interfaccia
  6. espressione
  7. chiamata costruttore / creazione oggetti
  8. chiamata di funzione
  9. tipo di parametro template

Combinando questi elementi e riconoscendo i loro confini, puoi creare quasi tutti gli stili di programmazione che desideri all'interno di c ++.

Un esempio di questo è per una funzione sarebbe riconoscere che è male chiamare altre funzioni da una funzione, perché causa la dipendenza, invece, dovresti solo chiamare le funzioni membro dei parametri della funzione originale.

    
risposta data 30.08.2011 - 23:47
fonte
-1

La più grande sfida tecnica è il problema dello spazio dei nomi. Il collegamento parziale può essere utilizzato per ovviare a questo problema. L'approccio migliore è progettare utilizzando gli standard di codifica. Altrimenti tutti i simboli diventano un disastro.

    
risposta data 30.08.2011 - 20:59
fonte
-2

Emacs è un buon esempio di questo:

ItestEmacsLisputilizzanoskip-unlesselet-bindpereseguirerilevamentidifunzionalitàedispositividiprova:

Sometimes,itdoesn'tmakesensetorunatestduetomissingpreconditions.ArequiredEmacsfeaturemightnotbecompiledin,thefunctiontobetestedcouldcallanexternalbinarywhichmightnotbeavailableonthetestmachine,younameit.Inthiscase,themacroskip-unlesscouldbeusedtoskipthetest:

(ert-deftesttest-dbus()"A test that checks D-BUS functionality."
   (skip-unless (featurep 'dbusbind))
   ...)

The outcome of running a test should not depend on the current state of the environment, and each test should leave its environment in the same state it found it in. In particular, a test should not depend on any Emacs customization variables or hooks, and if it has to make any changes to Emacs's state or state external to Emacs (such as the file system), it should undo these changes before it returns, regardless of whether it passed or failed.

Tests should not depend on the environment because any such dependencies can make the test brittle or lead to failures that occur only under certain circumstances and are hard to reproduce. Of course, the code under test may have settings that affect its behavior. In that case, it is best to make the test let-bind all such setting variables to set up a specific configuration for the duration of the test. The test can also set up a number of different configurations and run the code under test with each.

Come è SQLite. Ecco il suo design:

  1. sqlite3_open () → Apre una connessione a un database SQLite nuovo o esistente. Il costruttore per sqlite3.

  2. sqlite3 → L'oggetto di connessione al database. Creato da sqlite3_open () e distrutto da sqlite3_close ().

  3. sqlite3_stmt → L'oggetto statement preparato. Creato da sqlite3_prepare () e distrutto da sqlite3_finalize ().

  4. sqlite3_prepare () → Compilare il testo SQL in byte-code che farà il lavoro di interrogare o aggiornare il database. Il costruttore per sqlite3_stmt.

  5. sqlite3_bind () → Memorizza i dati dell'applicazione in parametri dello SQL originale.

  6. sqlite3_step () → Anticipa un sqlite3_stmt alla riga del risultato successiva o al completamento.

  7. sqlite3_column () → I valori delle colonne nella riga del risultato corrente per uno sqlite3_stmt.

  8. sqlite3_finalize () → Destructor per sqlite3_stmt.

  9. sqlite3_exec () → Una funzione wrapper che fa sqlite3_prepare (), sqlite3_step (), sqlite3_column () e sqlite3_finalize () per una stringa di una o più istruzioni SQL.

  10. sqlite3_close () → Destructor per sqlite3.

TheTokenizer,Parser,andCodeGeneratorcomponentsareusedtoprocessSQLstatementsandconvertthemintoexecutableprogramsinavirtualmachinelanguageorbytecode.Roughlyspeaking,thesetopthreelayersimplementsqlite3_prepare_v2(). The byte code generated by the top three layers is a prepared statement. The Virtual Machine module is responsible for running the SQL statement byte code. The B-Tree module organizes a database file into multiple key/value stores with ordered keys and logarithmic performance. The Pager module is responsible for loading pages of the database file into memory, for implementing and controlling transactions, and for creating and maintaining the journal files that prevent database corruption following a crash or power failure. The OS Interface is a thin abstraction that provides a common set of routines for adapting SQLite to run on different operating systems. Roughly speaking, the bottom four layers implement sqlite3_step().

AvirtualtableisanobjectthatisregisteredwithanopenSQLitedatabaseconnection.FromtheperspectiveofanSQLstatement,thevirtualtableobjectlookslikeanyothertableorview.Butbehindthescenes,queriesandupdatesonavirtualtableinvokecallbackmethodsofthevirtualtableobjectinsteadofreadingandwritingonthedatabasefile.

Avirtualtablemightrepresentanin-memorydatastructures.OritmightrepresentaviewofdataondiskthatisnotintheSQLiteformat.Ortheapplicationmightcomputethecontentofthevirtualtableondemand.

Herearesomeexistingandpostulatedusesforvirtualtables:

Afull-textsearchinterfaceSpatialindicesusingR-TreesIntrospectthediskcontentofanSQLitedatabasefile(thedbstatvirtualtable)Readand/orwritethecontentofacomma-separatedvalue(CSV)fileAccessthefilesystemofthehostcomputerasifitwereadatabasetableEnablingSQLmanipulationofdatainstatisticspackageslikeR

UsiSQLitediunavarietàditecnicheditest,tracui:

Threeindependentlydevelopedtestharnesses100%branchtestcoverageinanas-deployedconfigurationMillionsandmillionsoftestcasesOut-of-memorytestsI/OerrortestsCrashandpowerlosstestsFuzztestsBoundaryvaluetestsDisabledoptimizationtestsRegressiontestsMalformeddatabasetestsExtensiveuseofassert()andrun-timechecksValgrindanalysisUndefinedbehaviorchecksChecklists

Riferimenti

risposta data 28.09.2018 - 19:47
fonte

Leggi altre domande sui tag