Qual è il più grande difetto di progettazione che hai affrontato in qualsiasi linguaggio di programmazione? [chiuso]


29

Tutti i linguaggi di programmazione hanno i loro difetti di progettazione semplicemente perché non un singolo linguaggio può essere perfetto, proprio come con la maggior parte (tutte?) Altre cose. A parte questo, quale difetto di progettazione in un linguaggio di programmazione ti ha infastidito di più nella tua storia di programmatore?

Nota che se una lingua è "cattiva" solo perché non è progettata per una cosa specifica non è un difetto di progettazione, ma una caratteristica del design, quindi non elencare tali fastidi delle lingue. Se una lingua non è adatta per quello per cui è stata progettata, questo è ovviamente un difetto nel design. Neanche le cose specifiche per l'implementazione e le cose nascoste.


6
Nota che questo è costruttivo per i progettisti di linguaggi (errori da evitare), se qualcuno vorrebbe mettere in discussione quanto sia costruttiva questa domanda.
Anto


1
@greyfade: Non proprio, si tratta di difetti nella lingua reale, che sembrano riguardare cose che abbassano l'adozione di una lingua, che potrebbe includere una libreria standard scadente o solo un sito Web negativo per la lingua. Alcune risposte elencano ad esempio una sintassi errata, ma non si tratta di un difetto di progettazione specifico
Anto

8
Il più grande difetto in qualsiasi linguaggio di programmazione? Gli esseri umani.
Joel Etherton

Se queste risposte sono i maggiori difetti, allora sono davvero colpito dai linguaggi di programmazione.
Tom Hawtin: affronta il

Risposte:


42

Uno dei miei grandi fastidi è il modo switchin cui i casi nei linguaggi derivati ​​da C passano al caso successivo se si dimentica di usare break. Comprendo che ciò è utile in un codice di livello molto basso (ad es. Duff's Device ), ma di solito è inappropriato per il codice a livello di applicazione ed è una fonte comune di errori di codifica.

Ricordo che nel 1995, quando stavo leggendo i dettagli di Java per la prima volta, quando arrivai alla parte switchsull'affermazione, rimasi molto deluso dal fatto che avessero mantenuto il comportamento di fall-through predefinito. Questo si trasforma switchin un glorificato gotocon un altro nome.


3
@Christopher Mahan: switchnon deve funzionare in questo modo. Ad esempio, l' istruzione case / when di Ada (equivalente a switch / case) non ha un comportamento fall-through.
Greg Hewgill

2
@Greg: switchistruzioni simili in lingue non correlate a C non devono funzionare in questo modo. Ma se si utilizza il flusso di controllo in stile C ( {... }, for (i = 0; i < N; ++i), return, etc.), l'inferenza lingua farà la gente si aspetta switchdi lavoro come C, e dandogli Ada / Pascal / persone BASIC-come la semantica avrebbe confuso. C # richiede breaknelle switchdichiarazioni per lo stesso motivo, sebbene lo renda meno soggetto a errori impedendo la caduta silenziosa. (Ma vorrei che tu potessi scrivere fall;invece del brutto goto case.)
dan04

4
quindi non scrivere switch, scrivi if () else. il motivo per cui ti lamenti è ciò che rende il passaggio migliore: non è un condizionale vero / falso, è un condizionale numerale e questo lo rende diverso. Tuttavia, puoi anche scrivere la tua funzione switch.
jokoon

9
-1 Considero un vantaggio consentire il passaggio, non vi è alcun pericolo se non la stupidità, ma garantisce una funzione aggiuntiva.
Orbling

11
Il problema non è che switch consente il fallthrough. È che la maggior parte degli usi del fallthrough non sono intenzionali.
dan04

41

Non mi è mai piaciuto l'uso di =per assegnazione e ==test di uguaglianza nei linguaggi derivati ​​da C. Il potenziale di confusione ed errori è troppo elevato. E non farmi nemmeno iniziare ===in Javascript.

Meglio sarebbe stato :=per il compito e =per la verifica dell'uguaglianza. La semantica avrebbe potuto essere esattamente la stessa di oggi, in cui l'assegnazione è un'espressione che produce anche un valore.


3
@Nemanja Trifunovic: inizialmente avevo pensato a quel suggerimento, ma ha una sfortunata ambiguità in C con un confronto minore rispetto a un numero negativo (es. x<-5). I programmatori C non tollererebbero quel tipo di spazio bianco richiesto :)
Greg Hewgill

7
@Greg: Preferirei :=e ==perché sarebbe troppo facile dimenticare :e non essere avvisato come se fosse già il caso (anche se ripristinato) quando si dimentica un =oggi. Sono grato per gli avvisi del compilatore su questo ...
Matthieu M.

13
Su quasi tutte le tastiere che abbia mai usato, ": =" richiede la modifica del tasto Maiusc durante la digitazione. In quello che sto usando ora, ':' è maiuscolo e '=' è minuscolo, e l'ho fatto invertire. Scrivo molti compiti e non ho bisogno di quel tipo di seccatura di digitazione in essi.
David Thornley,

14
@ David Thornley: il codice viene letto molte più volte di quanto sia scritto. Non compro nessun argomento sul "fastidio di battitura".
Greg Hewgill

8
@Greg Hewgill: certo che viene letto più spesso di quanto scritto. Tuttavia, il problema tra =e ==non è nella lettura, perché sono simboli distinti. È scritto e assicurato che tu abbia quello giusto.
David Thornley,

29

La scelta di +in Javascript sia per l' aggiunta che per la concatenazione di stringhe è stata un terribile errore. Poiché i valori non sono tipizzati, ciò porta a regole bizantine che determinano se +aggiungere o concatenare, a seconda del contenuto esatto di ciascun operando.

Inizialmente sarebbe stato facile introdurre un operatore completamente nuovo, ad esempio $per la concatenazione di stringhe.


13
@ Barry: non proprio. +ha molto senso come operatore di concatenazione di stringhe in un linguaggio fortemente tipizzato. Il problema è che Javascript lo utilizza ma non è fortemente tipizzato.
Mason Wheeler

13
@ Mason: +non ha senso per la concatenazione, perché la concatenazione non è sicuramente commutativa. È un abuso per quanto mi riguarda.
Matthieu M.

12
@Matthieu: Umm ... perché è importante? La concatenazione non è commutativa (come l'aggiunta), ma l'aggiunta di due stringhe è senza senso, quindi nessuno la pensa in questo modo. Stai inventando un problema in cui non esiste nessuno.
Mason Wheeler,

4
passa a C ++ e aggiungi qualsiasi tipo di sovraccarico esoterico all'operatore che
preferisci

8
@Matthieu: la commutatività non è il problema qui. Se JS avesse una classe Matrix, considereresti un abuso avere un *operatore?
dan04

24

Trovo Javascript predefinito a livello globale per un grosso problema e spesso fonte di bug se non si utilizza JSLint o simili


2
L'impostazione predefinita è globale? Sono contento di non usare JS allora ...
Anto

5
In realtà è un linguaggio molto carino, con alcune cattive scelte di design. Questo è il principale, se non si applica una variabile, verrà portata come globale. La cosa buona è che usando il programma Jslint di Doug Crockford è possibile rilevare questo errore e molti altri.
Zaccaria K

1
Basta usare varogni volta che dichiari una variabile e sei a posto. E non ditemi che è troppo scrivere perché Java vi costringe a dichiarare tutti i tipi due volte e nessuno si lamenta del fatto che sia una scelta merdosa di design.
davidk01

3
@ davidk01: le persone se ne lamentano. Allo stesso modo, le persone si sono lamentate di dover dichiarare std::map<KEY, VALUE>::const_iteratorabbastanza variabili in C ++ da autoaggiungere a quella lingua.
dan04

1
la "use strict"direttiva, aggiunta in es5, trasforma i riferimenti non dichiarati in un errore.
Sean McMillan,

22

Il preprocessore in C e C ++ è un kludge massiccio, crea astrazioni che perdono come setacci, incoraggia il codice spaghetti attraverso i nidi di #ifdefdichiarazioni di ratto e richiede ALL_CAPSnomi orribilmente illeggibili per aggirare i suoi limiti. La radice di questi problemi è che opera a livello testuale piuttosto che a livello sintattico o semantico. Avrebbe dovuto essere sostituito con funzionalità di linguaggio reale per i suoi vari casi d'uso. Ecco alcuni esempi, anche se è vero che alcuni di questi sono risolti in C ++, C99 o estensioni non ufficiali ma di fatto standard:

  • #include avrebbe dovuto essere sostituito con un sistema di moduli reale.

  • Le funzioni e i modelli / generici incorporati potrebbero sostituire la maggior parte dei casi d'uso delle chiamate di funzione.

  • Per dichiarare tali costanti è possibile utilizzare una sorta di caratteristica di costante di tempo manifest / compilare. Le estensioni di enum di D funzionano benissimo qui.

  • Le macro a livello di albero della sintassi reale potrebbero risolvere molti casi d'uso vari.

  • I mixin di stringhe potrebbero essere utilizzati per il caso d'uso dell'iniezione di codice.

  • static ifo versiondichiarazioni potrebbero essere utilizzate per la compilazione condizionale.


2
@dsimcha: sono d'accordo con il #includeproblema, ma il sistema del modulo è stato inventato ... in seguito! E C e C ++ mirano ad un massimo di compatibilità con le versioni precedenti: /
Matthieu M.

@Matthieu: Sì, i sistemi di moduli sono stati inventati dopo le parole, ma stiamo comunque parlando di senno di poi.
dsimcha,

3
Concordo sul fatto che questo è un dolore enorme in varie parti del corpo, ma il design multi-pass ha il vantaggio della semplicità: un compilatore C non ha bisogno di sapere molto sul contesto dell'operazione prima di poter compilare con successo un pezzo di codice C . Questo può essere qualificato come errore di progettazione solo se si può dimostrare che i costi dell'utilizzo di un ipotetico sistema di moduli all'interno del linguaggio C stesso (ad es. Classi simili a C ++) sono sempre inferiori o comparabili all'attuale #includehacking basato su cpp .
reinierpost

Sono d'accordo. Tuttavia, ma alcune persone pensano che la preelaborazione sia una buona cosa e si lamentano del fatto che non è supportato (ad esempio) in Java.
Stephen C

5
@Stephen: concordo sul fatto che Java con un preprocessore potrebbe essere migliore di Java senza, ma solo perché Java non ha molte delle funzionalità "reali" necessarie per sostituire il preprocessore. In linguaggi come D, che includono tali funzionalità e Python, che ottiene flessibilità in altri modi essendo dinamico, non mi manca un po '.
dsimcha,

21

Si potrebbero elencare centinaia di errori in centinaia di lingue, ma IMO non è un esercizio utile dal punto di vista della progettazione linguistica.

Perché?

Perché qualcosa che sarebbe un errore in una lingua non sarebbe un errore in un'altra lingua. Per esempio:

  • Rendere C una lingua gestita (cioè raccolta dei rifiuti) o legare i tipi primitivi ne limiterebbe l'utilità come linguaggio semi-portatile di basso livello.
  • L'aggiunta della gestione della memoria in stile C a Java (ad esempio, per risolvere i problemi di prestazioni) la interromperebbe.

Ci sono lezioni da imparare, ma le lezioni raramente sono ben definite e per capirle devi capire i compromessi tecnici ... e il contesto storico. (Ad esempio, l'implementazione ingombrante di generici da parte di Java è una conseguenza di un requisito aziendale prevalente per mantenere la retrocompatibilità.)

IMO, se sei seriamente intenzionato a progettare una nuova lingua, devi effettivamente utilizzare una vasta gamma di lingue esistenti (e studiare lingue storiche) ... e decidere quali sono gli errori. E devi tenere a mente che ognuna di queste lingue è stata progettata in un particolare contesto storico, per colmare un bisogno particolare.

Se ci sono lezioni generali da imparare, queste sono al livello "meta":

  • Non è possibile progettare un linguaggio di programmazione ideale per tutti gli scopi.
  • Non puoi evitare di commettere errori ... specialmente se visto da dietro.
  • Molti errori sono dolorosi da correggere ... per gli utenti della tua lingua.
  • Devi tener conto del background e delle abilità del tuo pubblico target; cioè programmatori esistenti.
  • Non puoi piacere a tutti.

9
-1 - i linguaggi di programmazione seguono gli obiettivi di progettazione. Una caratteristica di un linguaggio che lavora contro questi obiettivi è un difetto di progettazione, a meno che non sia un compromesso necessario per uno dei suoi altri obiettivi. Non molte lingue sono create con l'intenzione di soddisfare tutti, ma tutte le lingue dovrebbero tentare di soddisfare quelle persone che si prefigge di soddisfare in primo luogo. Questo tipo di correttezza politica postmoderna è abbastanza soffocante per la programmazione della ricerca e dello sviluppo del linguaggio.
Rei Miyasaka,

Il mio punto è che è difficile imparare le lezioni senza tener conto degli obiettivi di progettazione.
Stephen C

1
mi sembra che l'OP abbia già rappresentato la tua risposta nel secondo paragrafo dell'interrogazione.
Aidan Cully

20

C e C ++ : tutti quei tipi interi che non significano nulla.

Soprattutto char. È testo o è un numero intero minuscolo? Se è testo, è un carattere "ANSI" o un'unità di codice UTF-8? Se è un numero intero, è firmato o non firmato?

int era inteso per essere il numero intero "nativo", ma sui sistemi a 64 bit, non lo è.

longpuò essere o non essere più grande di int. Può essere o meno la dimensione di un puntatore. È praticamente una decisione arbitraria da parte degli autori del compilatore sia che si tratti di 32 bit o 64 bit.

Sicuramente una lingua degli anni '70. Prima di Unicode. Prima dei computer a 64 bit.


10
C non è stato progettato per essere un linguaggio standard adatto a tutti, quindi non può essere un errore di progettazione. È stato progettato per modellare una CPU come assemblatore portatile evitando il codice assembler specifico della CPU. Tuttavia, è stato risolto in Java.

7
Penso che il problema più grande siano le nuove lingue che continuano a usare gli stessi termini insignificanti, nonostante la storia ci mostri che è un'idea terribile. Almeno i ragazzi del C hanno notato il loro errore e creato tipi int standard.
Segna H

5
Un carattere non è un'unità utf-8. Un carattere utf-8 può richiedere più di 8 bit per la memorizzazione. C non è un linguaggio degli anni '70, lo sto usando per un progetto ora (volontariamente).
dan_waterworth

4
C è poco più di un'astrazione di alto livello del processore PDP-11. Ad esempio, l'incremento pre e post era direttamente supportato dal PDP-11.
bit-twiddler

5
Questa è una risposta terribilmente sbagliata. Prima di tutto, C e C ++ non sono intercambiabili. In secondo luogo, le specifiche del linguaggio definiscono chiaramente che cos'è un carattere: un oggetto dichiarato come tipo carattere è abbastanza grande da contenere qualsiasi membro del set di caratteri di esecuzione di base. . Terzo, C non è un "linguaggio degli anni '70", è un linguaggio che vive vicino all'hardware ed è probabilmente il linguaggio che alla fine consente a tutte le tue astrazioni di alto livello di avere un senso per una CPU. Vieni come una persona che conosce solo lingue di alto livello e non apprezza il modo in cui le cose funzionano davvero. -1
Ed S.

18

null.

Il suo inventore, Tony Hoare, lo definisce "l'errore di miliardi di dollari" .

È stato introdotto in ALGOL negli anni '60 ed esiste oggi nella maggior parte dei linguaggi di programmazione comunemente usati.

L'alternativa migliore, usata in lingue come OCaml e Haskell, è forse . L'idea generale è che i riferimenti agli oggetti non possono essere nulli / vuoti / inesistenti a meno che non vi sia un'indicazione esplicita che potrebbero essere così.

(Anche se Tony è fantastico nella sua modestia, penso che quasi tutti avrebbero commesso lo stesso errore, e per caso è stato il primo.)


Non sono d'accordo, anche con il suo inventore !!! null è il valore vuoto per il tipo di dati puntatore / riferimento. Strings ha stringa vuota, set ha set vuoto (Pascal empty set = []), numeri interi ha 0. La maggior parte dei linguaggi di programmazione che usano null / nil / qualunque, se una variabile è assegnata correttamente, può essere evitato un errore null.
Umlcat,

4
@ user14579: ogni lingua che supporta qualsiasi tipo di set, stringa o matrice ha {}, ma tale è ancora semanticamente appropriato e non si arresta in modo anomalo a meno che tu non abbia già qualcosa che potrebbe causare un errore dei limiti dell'array, ma questo è un altro problema. È possibile elaborare una stringa vuota per mettere in maiuscolo tutti i caratteri, il che si tradurrà in una stringa vuota. Provi la stessa cosa su una stringa nulla e, senza un'adeguata considerazione, si bloccherà. Il problema è che questa giusta considerazione è noiosa, spesso dimenticata, e rende difficile scrivere funzioni a espressione singola (cioè lambda).
Rei Miyasaka,

1
Faccio soldi ogni volta che scrivo null ... oh giusto qualcuno perde soldi ogni volta che scrivo null. Tranne questa volta.
Kevin il

3
@umlcat - quando hai lingue con pattern matching come Ocaml, Haskell e F #, usando forse x | Nessuno schema ti impedisce di dimenticare il caso nullo al momento della compilazione. Nessuna quantità di inganno durante la compilazione può rilevare un errore nelle lingue in cui null è l'idioma stabilito. Dato che devi scegliere esplicitamente di non trattare il caso null in lingue che hanno la monade Maybe e Some, hanno un serio vantaggio rispetto all'approccio "null".
JasonTrue,

1
@Jason - Mi piace pensare maybecome un opt-in null, mentre l'eccezione null è un opt-out. Naturalmente ci sono alcune cose da dire sulla differenza tra errori di runtime ed errori di compilazione, ma solo il fatto che null stia essenzialmente iniettando comportamenti è degno di nota in sé.
Rei Miyasaka,

14

Ho la sensazione che le persone che hanno progettato PHP non usassero una tastiera normale, non usassero nemmeno una tastiera colemak, perché avrebbero dovuto capire cosa stavano facendo.

Sono uno sviluppatore di PHP. PHP non è divertente da digitare.

Who::in::their::right::mind::would::do::this()? L' ::operatore deve tenere premuto il tasto Maiusc e quindi premere due tasti. Che spreco di energia.

Although-> this-> è-> non-> molto-> meglio. Ciò richiede anche tre pressioni di tasti con lo spostamento tra i due simboli.

$last = $we.$have.$the.$dumb.'$'.$character. Il simbolo del dollaro viene utilizzato moltissime volte e richiede che il premio si estenda fino alla cima della tastiera più una pressione del tasto Maiusc.

Perché non potevano progettare PHP per usare chiavi che sono molto più veloci da digitare? Perché non è possibile we.do.this()o avere varati iniziare con una chiave che richiede solo un singolo tasto - o per niente (JavaScript) e semplicemente pre-definire tutti i var (come devo fare comunque per E_STRICT)!

Non sono una dattilografa lenta, ma questa è solo una scelta di design scadente.


Perl condivide questo dolore.
Daenyth,

2
Così fa C ++, e per qualche inspiegabile ragione bizzarra, PowerShell.
Rei Miyasaka,

2
Usa un editor più potente e definisci le tue sequenze di tasti per quegli operatori.
Kevin Cline,

1
I::have::nothing::against::this->at.all()
Mateen Ulhaq,

1
Forse non usano tastiere qwerty. $ e: non è necessario premere il tasto MAIUSC su tutte le tastiere azerty come.
Arkh,

13

L'uso di forme ispirate al desktop all'interno di asp.net .

È sempre sembrato un pasticcio e si è messo in mezzo o il modo in cui funziona davvero il web. Per fortuna asp.net-mvc non soffre allo stesso modo, anche se con merito a Ruby ecc. Per quell'ispirazione.


18
Non è una cosa da biblioteca?
Nikie

@nikie è un buon punto;)
dove

1
@nikie In realtà, il codice ASPX basato su XML è un linguaggio, quindi puoi farlo girare per farlo funzionare. : D
CodexArcanum

2
Il vero problema con ASP.NET, penso, è quanto sia difficile cercare di nascondere i dettagli del web al programmatore. In ASP.NET ci sono davvero cose utili e pulite, ma devi combattere così tanto e scavare così in profondità per riuscirci.
CodexArcanum

1
D'altra parte ci sono migliaia e migliaia di app di raccolta dati semplici e di successo là fuori che sono state messe insieme utilizzando la cosa dell'app desktop "classica". L'unica cosa negativa era che fino a MVC l'unica opzione di Microsoft erano i moduli di Windows.
ElGringoGrande,

13

Per me è l'assoluta mancanza di PHP di convenzioni di denominazione e ordinamento degli argomenti nella sua libreria standard.

Sebbene la necessità di JASS di annullare i riferimenti dopo che l'oggetto referenziato è stato rilasciato / rimosso (o il riferimento perderebbe e diversi byte di memoria andrebbero persi) è più grave, ma poiché JASS è un linguaggio a scopo singolo, non è così critico.


9
La mancanza di convenzioni nello stdlib di PHP non è probabilmente un difetto di progettazione del linguaggio .

3
@delnan: la mancanza di convenzioni è il risultato di come è stato progettato PHP e quindi ha molto a che fare con il design del linguaggio. Inoltre, non mi è chiaro che esiste una chiara distinzione tra biblioteche e lingua. Lisp in particolare ha una fiera tradizione di bootstrap di una lingua sopra un'altra.
btilly

1
La cosa davvero notevole di JASS era che aveva un conteggio dei riferimenti sugli handle, ma non li avrebbe ripuliti a meno che non fossero stati distrutti manualmente (e l'interfaccia grafica non avesse creato funzioni che perdevano memoria dappertutto)!
Craig Gidney,

13

Il più grande difetto di progettazione che devo affrontare è che Python non è stato progettato come Python 3.x per cominciare.


1
Bene, nemmeno Guido riesce a

5
@delnan, oh lo so, e python <3 è ancora un linguaggio incredibilmente buono, ma è un po 'fastidioso avere un linguaggio migliore sotto forma di Python 3.x che non posso usare perché rompe tutti i moduli che Ho bisogno.
dan_waterworth,

Continua a fare lobby per i tuoi moduli Python 3.x! Nel frattempo continuerò a scrivere in 2.5.4. Grazie a SO mi viene in realtà ricordato che 3.x è vivo e vegeto.
Kevin il

@kevpie Per prima cosa, fai pressione nella lobby per aggiungere una compilazione condizionale a Python per facilitare la transizione per i manutentori della biblioteca. 2to3 non è una soluzione sostenibile a lungo termine.
Evan Plaice,

12

Decadimento della matrice in C e di conseguenza in C ++.


Vorrei anche un adeguato supporto dell'array. In C ++ puoi prevenire il decadimento usando la sintassi e i template degli abscons ... ma è più un hack: /
Matthieu M.

1
Si noti che questa è la ragione per cui C ++ deve avere separati deletee delete[]operatori.
dan04

Puoi sempre mettere una matrice in una struttura e farla passare per valore se vuoi, ma di solito è più imbarazzante del problema originale. In C ++, in genere è possibile evitare la necessità di utilizzare array.
David Thornley,

2
Almeno nel caso C, l'argomento contro il corretto supporto dell'array è "il controllo dei limiti dell'array è costoso", in particolare dato il modo in cui funziona l'aritmetica del puntatore C.
Stephen C

@Stephen C: Quale controllo dei limiti dell'array ha a che fare con il decadimento dell'array?
Nemanja Trifunovic

11

Tipi primitivi in ​​Java.

Infrangono il principio secondo cui tutto è un discendente java.lang.Object, che da un punto di vista teorico porta a un'ulteriore complessità delle specifiche del linguaggio e da un punto di vista pratico rendono l'uso delle raccolte estremamente noioso.

L'autoboxing ha contribuito ad alleviare gli svantaggi pratici, ma a costo di rendere le specifiche ancora più complicate e di introdurre una grossa buccia di banana grassa: ora puoi ottenere un'eccezione del puntatore null da quella che sembra una semplice operazione aritmetica.


Provocherebbe un terribile problema di prestazioni se si rimuovessero i tipi di primitive. E l'autoboxing può essere incasinato abbastanza facilmente, quindi non migliora nulla.
deadalnix,

All'epoca, questa era una decisione sensata dai progettisti Java. I miglioramenti delle prestazioni dati dalle macchine / macchine virtuali disponibili negli anni '90 hanno superato i vantaggi concettuali dell'unificazione di tutto ciò che riguarda java.lang.Object.
Mikera,

10

Conosco meglio Perl, quindi lo prenderò.

Perl ha provato molte idee. Alcuni erano buoni. Alcuni erano cattivi. Alcuni erano originali e non ampiamente copiati per una buona ragione.

Una è l'idea del contesto : ogni chiamata di funzione ha luogo in un elenco o in un contesto scalare e può fare cose completamente diverse in ogni contesto. Come ho sottolineato su http://use.perl.org/~btilly/journal/36756 questo complica ogni API e spesso porta a sottili problemi di progettazione nel codice Perl.

La prossima è l'idea di legare la sintassi e i tipi di dati in modo così completo. Ciò ha portato all'invenzione del legame per consentire agli oggetti di mascherarsi come altri tipi di dati. (Puoi anche ottenere lo stesso effetto usando il sovraccarico, ma il pareggio è l'approccio più comune in Perl.)

Un altro errore comune, fatto da molte lingue, è iniziare offrendo scoping dinamico piuttosto che lessicale. È difficile ripristinare questa decisione di progettazione in un secondo momento e porta a verruche di lunga durata. La descrizione classica di quelle verruche in Perl è http://perl.plover.com/FAQs/Namespaces.html . Si noti che questo è stato scritto prima che Perl aggiungesse ourvariabili e staticvariabili.

Le persone non sono legittimamente in disaccordo sulla tipizzazione statica rispetto a quella dinamica. Personalmente mi piace la digitazione dinamica. Tuttavia, è importante disporre di una struttura sufficiente per consentire la cattura di errori di battitura. Perl 5 fa un buon lavoro con severi. Ma Perl 1-4 ha sbagliato. Diverse altre lingue hanno pedine che fanno la stessa cosa di rigorose. Fintanto che sei bravo a far applicare il controllo dei filacci, questo è accettabile.

Se stai cercando altre idee cattive (molte), impara PHP e studia la sua storia. Il mio errore preferito in passato (risolto da tempo perché portava a così tante falle di sicurezza) era quello di consentire a chiunque di impostare qualsiasi variabile passando i parametri del modulo. Ma questo è tutt'altro che l'unico errore.


5
Sì, Perl ha molti errori, perché le persone che l'hanno costruito stavano provando nuove idee e quando lo fai spesso ti sbagli. (Perl ha anche delle cose molto buone, ed è lo standard per Regexps che tutti gli altri sembrano aver copiato)
Zachary K

@ zachary-k: assolutamente d'accordo. E ho provato a chiarirlo prima di iniziare a esaminare i problemi.
btilly

4
I Lisps erano originariamente con ambito dinamico, e nel tempo sono stati cambiati in ambito lessicale (almeno in Scheme e Common Lisp). Non è impossibile cambiare.
David Thornley,

4
@ David-Thornley: è impossibile se non sacrifichi la retrocompatibilità da qualche parte. Lo schema era sempre con ambito lessicale. Il Lisp comune è stato sotto il profilo lessicale dal momento in cui è stato standardizzato, ma varie comunità Lisp hanno avuto le loro lotte per adottarlo. Ed Emacs Lisp sta ancora usando l'ambito dinamico, anche se c'è stato il desiderio di cambiarlo per molto tempo.
btilly

1
A proposito, molte delle cose che non piacciono al Perl non sono state inventate in Perl ma prese da altre lingue, principalmente dalla shell Bourne.
reinierpost

10

Ambiguità di JavaScripts per blocchi di codice e valori letterali degli oggetti.

  {a:b}

potrebbe essere un blocco di codice, dove aè un'etichetta ed bè un'espressione; oppure potrebbe definire un oggetto, con un attributo ache ha il valoreb


In realtà mi piace questo su JavaScript. La semplicità della struttura linguistica è buona e lo scopo dovrebbe essere evidente se lo sviluppatore sa cosa sta facendo.
Xeoncross

2
Xeoncross: in genere non mi piacciono le ambiguità. In questo caso, è ovvio per lo sviluppatore, ma eval () ha bisogno di parentesi extra.
user281377

2
@ammoQ: è ovvio? Quindi cos'è? Un oggetto o un blocco di codice?
configuratore

configuratore: ovviamente un oggetto. Nessuna persona sana di mente userebbe un'etichetta chiamata a.
user281377

10

Tornerò a FORTRAN e all'insensibilità agli spazi bianchi.

Ha pervaso le specifiche. La ENDcarta doveva essere definita come una carta con una 'E', una 'N' e una 'D' in quell'ordine nelle colonne 7-72 ​​e nessun altro non bianco, piuttosto che una carta con "END" nella corretta colonne e nient'altro.

Ha portato a una facile confusione sintattica. DO 100 I = 1, 10era un'istruzione di controllo del ciclo, mentre DO 100 I = 1. 10era un'istruzione che assegnava il valore 1.1 a una variabile chiamata DO10I. (Il fatto che le variabili possano essere create senza dichiarazione, il loro tipo a seconda della loro prima lettera, ha contribuito a questo.) A differenza di altre lingue, non c'era modo di usare gli spazi per separare i token per consentire la chiarimento delle ambiguità.

Ha anche permesso ad altre persone di scrivere codice davvero confuso. Ci sono ragioni per cui questa funzione di FORTRAN non è mai stata duplicata mai più.


1
In una lingua in cui è possibile ridefinire i letterali, questo è l'esempio peggiore - voglio dire, ha causato solo un piccolo veicolo spaziale in crash
Martin Beckett

All'inizio, potresti scrivere DAMNATION al posto di DIMENSION e funzionerebbe.
Mike Dunlavey,

Viene ancora insegnato da persone al di fuori di CS. Devo ancora avere a che fare con coloro che a) combattono contro le dichiarazioni, b) combattono contro gli spazi bianchi, c) come "linee di continuazione", d) usano nomi di 6 caratteri o 4, d) sono sconcertati quando vedono (test ? a : b), e) insistono sull'uso **, f) non è in grado di gestire la distinzione tra maiuscole e minuscole. La maggior parte di questo è dovuto ai keypunch negli anni '50.
Mike Dunlavey,

1
@Martin Beckett - ridefinire i letterali in FORTRAN è stato davvero un difetto del compilatore piuttosto che una caratteristica del linguaggio. Certamente non era una caratteristica del linguaggio intenzionale.
Stephen C

1
@oosterwal: certamente. Potrei sbagliarmi, ma ricordo vagamente la definizione della lingua basata su schede perforate. Erano il modo principale per introdurre i programmi FORTRAN a quel tempo, e l'idea di una linea a 80 colonne con colonne 73-80 riservate è da schede perforate.
David Thornley,

9

Uno dei maggiori problemi con BASIC è stata la mancanza di un metodo ben definito per estendere il linguaggio oltre i suoi primi ambienti, portando a una serie di implementazioni completamente incompatibili (e un tentativo post-facto quasi irrilevante di qualsiasi standardizzazione).

Quasi ogni lingua verrà piegata all'uso generico da parte di un programmatore pazzo. È meglio pianificare l'uso generico all'inizio nel caso in cui l'idea folle decolla.


2
+1: Qualsiasi lingua senza moduli e librerie adeguati è un errore che attende di accadere. Anche COBOL ne ha risentito, portando a varianti peculiari non compatibili.
S.Lott

8

Credo nei DSL (linguaggi specifici del dominio) e una cosa che apprezzo in una lingua è se mi permette di definire un DSL al di sopra di esso.

In Lisp ci sono macro: la maggior parte delle persone lo considera una buona cosa, così come io.

In C e C ++ ci sono macro - le persone si lamentano di loro, ma sono stato in grado di usarle per definire DSL.

In Java, sono stati esclusi (e quindi in C #) e la loro mancanza è stata dichiarata una virtù. Certo, ti permette di avere intellisense, ma per me è solo un'opera d' arte . Per fare il mio DSL, devo espandermi a mano. È una seccatura e mi fa sembrare un cattivo programmatore, anche se mi permette di fare molto di più con tonnellate di codice in meno.


4
Concordo sul fatto che qualsiasi linguaggio senza macro decenti è un enorme difetto di progettazione non risolvibile. Ma cosa intendi con " sono stati esclusi "? Il preprocessore C non era un tipo di sistema macro decente. Java non deriva da alcun linguaggio appropriato con le macro.
Logica SK

1
Puoi scrivere il tuo DSL in un linguaggio di elaborazione macro esterno (come m4, diciamo, tra una miriade di altri).
SOLO IL MIO OPINIONE corretta,

4
@SK: Non dirò che il preprocessore C è un sistema macro decente rispetto a Lisp (per esempio). Ma, rispetto a niente , è estremamente utile.
Mike Dunlavey,

4
@reinierpost: sto pensando a cose che potrei fare in Lisp, come introdurre strutture di controllo come esecuzione differenziale e backtrack. Questi potrebbero essere fatti con le macro Lisp. In C / C ++ ho potuto eseguire un'esecuzione differenziale con macro C (e una piccola disciplina del programmatore), ma non tornare indietro. Con C # non posso fare nessuno dei due. Ciò che ottengo in cambio sono cose come intellisense. BFD.
Mike Dunlavey,

1
@ David: Il modo in cui l'ho fatto era che avevo una macro per racchiudere il codice ordinario, come un elenco di istruzioni. Prenderebbe la cdrlista e ne formerebbe una chiusura lambda (cioè una continuazione) e la passerebbe come argomento alla carlista. Ciò è stato fatto in modo ricorsivo, ovviamente, e avrebbe "fatto la cosa giusta" per condizionali, loop e chiamate di funzione. Quindi la funzione "scelta" si è appena trasformata in un normale loop. Non carino, ma era robusto. Il problema è che rende semplicissimo realizzare loop troppo nidificati.
Mike Dunlavey,

7

Dichiarazioni , in ogni lingua che le possiede. Non fanno nulla che tu non possa fare con le espressioni e ti impediscono di fare molte cose. L'esistenza di un ?:operatore ternario è solo un esempio di come cercare di aggirarli. In JavaScript, sono particolarmente fastidiosi:

// With statements:
node.listen(function(arg) {
  var result;
  if (arg) {
    result = 'yes';
  } else {
    result = 'no';
  }
  return result;
})

// Without:
node.listen(function(arg) if (arg) 'yes' else 'no')

Sono confuso qui: vuoi solo un modo più semplice di fare le cose?
TheLQ

2
Corretta. Espressioni per tutto.
munifico

1
Lisp funziona bene per questo.
David Thornley,

1
@ SK-logic: sospetto che le dichiarazioni siano state ereditate ciecamente dal linguaggio macchina, attraverso FORTRAN, ALGOL e COBOL.
David Thornley,

1
Sono abbastanza sicuro che il linguaggio macchina sia l'antenato comune, e questo è solo un riflesso del fatto che i computer moderni basati sull'architettura von Neumann eseguono le istruzioni in sequenza e modificano lo stato. Alla fine, quando si verifica IO, ci saranno espressioni che non generano dati significativi, quindi le dichiarazioni non sono del tutto inutili nell'indicare semanticamente che alcuni codici hanno solo effetti collaterali. Anche le lingue che hanno una nozione di unittipo (aka ()) invece di dichiarazioni hanno una considerazione speciale per assicurarsi che non lancino avvertimenti o si comportino in modo strano.
Rei Miyasaka,

6

Per me, è il problema di progettazione che affligge tutti i linguaggi derivati ​​da C; vale a dire l '" altro penzolante ". Questo problema grammaticale avrebbe dovuto essere risolto in C ++, ma è stato portato avanti in Java e C #.


3
Uno degli obiettivi principali del C ++ era di essere pienamente compatibile con le versioni precedenti di C. Se avessero cambiato drasticamente il comportamento semantico, potrebbe non aver preso piede come aveva fatto (o almeno, quello era il pensiero in quel momento)
Ed S.

2
@Ed S., tuttavia, l'eliminazione del problema "penzoloni" avrebbe potuto essere realizzata eliminando la produzione grammaticale <compound_statement> (aka <block>) e incorporando le parentesi graffe nelle strutture di controllo condizionali e iterative come hanno fatto quando hanno aggiunta la struttura di controllo della gestione delle eccezioni try / catch. Non ci sono scuse per non correggere questa ambiguità grammaticale in Java e C #. Attualmente, la soluzione difensiva per questa ambiguità grammaticale consiste nel rendere ogni istruzione che segue un'istruzione di controllo condizionale o iterativa un'istruzione composta.
bit-twiddler,

1
Che cosa vuoi dire con questo? Questo non è un "problema grammaticale" (è completamente inequivocabile). Come lo "risolveresti"? In realtà trovo soddisfacenti le regole in C. Probabilmente, solo una sintassi simile a Python (= indentazione significativa) può davvero risolvere questo problema. Inoltre, sono davvero molto felice che le lingue moderne non impongano parentesi graffe. Concordo sul fatto che tutte le sintassi simili a C fanno schifo ma penzoloni, altrimenti è il minore dei loro problemi.
Konrad Rudolph,

1
Continua: Penso che l'uso da parte di Python di un rientro come mezzo per delineare un elenco di affermazioni sia strano oltre ogni immaginazione. Questa tecnica viola il principio di "separazione delle preoccupazioni" mediante una stretta associazione della scansione lessicale con l'analisi della sintassi. Una grammatica senza contesto dovrebbe essere in grado di essere analizzata senza sapere nulla sul layout della fonte.
bit-twiddler

3
@ bit-twiddler: No, non lo è. Il lexer Python converte semplicemente lo spazio bianco nei token INDENT e DEDENT appropriati. Una volta fatto, Python ha una grammatica piuttosto convenzionale ( docs.python.org/reference/grammar.html ).
dan04

6

Penso che tutte le risposte finora indicano un singolo fallimento di molte lingue tradizionali:

Non è possibile modificare la lingua principale senza influire sulla compatibilità con le versioni precedenti.

Se questo viene risolto, praticamente tutti gli altri problemi possono essere risolti.

MODIFICARE.

questo può essere risolto nelle biblioteche con spazi dei nomi diversi e potresti pensare di fare qualcosa di simile per la maggior parte del nucleo di una lingua, anche se ciò potrebbe significare che devi supportare più compilatori / interpreti.

In definitiva, non credo di sapere come risolverlo in un modo totalmente soddisfacente, ma ciò non significa che non esiste una soluzione o che non si può fare di più


1
Come faresti per "risolvere" questo?
Bjarke Freund-Hansen,

Non sono sicuro che possa essere completamente risolto - ovviamente mantenere le cose fuori dal linguaggio principale e in una libreria standard aiuta quindi penso che proverei a spingerlo il più lontano possibile
jk.

1
Per retrocompatibilità, vuoi dire che i compilatori più recenti dovrebbero essere in grado di compilare il vecchio codice?
Rei Miyasaka,

Voglio dire, i compilatori più recenti non dovrebbero cambiare il significato del vecchio codice, non riuscire a compilare sarebbe un sottoinsieme di quello
jk.

la maggior parte dei casi, quando una particolare funzionalità non esiste o desidera cambiare, le persone creano una nuova lingua basata sulla precedente. C con classi => C ++
umlcat


4

Sia Java che C # hanno fastidiosi problemi con i loro sistemi di tipi a causa del desiderio di mantenere la retrocompatibilità durante l'aggiunta di generici. A Java non piace mescolare generici e array; C # non consente alcune firme utili perché non è possibile utilizzare i tipi di valore come limiti.

Come esempio di quest'ultimo, consideralo

public static T Parse <T> (tipo <T> tipo, stringa str) dove T: Enum
a fianco o in sostituzione
oggetto statico pubblico Parse (Type type, string str)
nella Enumclasse consentirebbe
MyEnum e = Enum.Parse (typeof (MyEnum), str);
piuttosto che tautologico
MyEnum e = (MyEnum) Enum.Parse (typeof (MyEnum), str);

tl; dr: pensa al polimorfismo parametrico quando inizi a progettare il tuo sistema di tipi, non dopo aver pubblicato la versione 1.


2
L'incapacità di limitare i tipi a enum è fastidiosa in C #, ma puoi in qualche modo aggirarlo in questo modo MyMethod<T>(T value) where T : struct, IComparable, IFormattable, IConvertible Ma devi ancora testare un enum ed è un hack. Penso che la più grande mancanza nei generici C # non sia il supporto per i tipi superiori, che aprirebbe davvero il linguaggio ad alcuni concetti interessanti.
CodexArcanum

4

Mi sento come se mi stessi aprendo per essere fiammeggiato, ma odio davvero la capacità di passare semplici vecchi tipi di dati per riferimento in C ++. Odio solo leggermente essere in grado di passare tipi complessi per riferimento. Se sto guardando una funzione:

void foo()
{
    int a = 8;
    bar(a);
}

Dal punto di chiamata, non c'è modo di dire che bar, che può essere definito in un file completamente diverso, è:

void bar(int& a)
{
    a++;
}

Qualcuno potrebbe obiettare che fare qualcosa del genere potrebbe essere solo una cattiva progettazione del software e non dare la colpa alla lingua, ma non mi piace che la lingua ti permetta di farlo in primo luogo. Usando un puntatore e chiamando

bar(&a);

è molto più leggibile.


+1 Non sono d'accordo con te, ma apprezzo il tuo ragionamento.
Jon Purdy,

@Jon In realtà sarei molto interessato a quello che pensi. Hai la vista "non dare la colpa alla lingua"?
Jeff

6
@Jeff: Per prima cosa, il motivo principale per cui la semantica di riferimento si è fatta strada nel C ++ è stato il sovraccarico dell'operatore, per il quale un comportamento di riferimento uniforme ha semplicemente senso. Ancora più importante, tuttavia, C ++ è progettato per essere versatile e fornire funzionalità molto dettagliate, anche se ciò comporta un rischio significativo di errore del programmatore. Quindi sì, almeno in questo caso particolare, non dare la colpa alla lingua. Preferirei essere in grado di commettere errori piuttosto che lasciare che una lingua mi si frapponga.
Jon Purdy

@Jon concordato, sarebbe molto strano che il pass per riferimento si applica a tutto tranne che ai POD. Preferirei che questa funzionalità mancasse interamente dal C ++ in alternativa, ma possiamo essere d'accordo di non essere d'accordo :). Grazie per l'input!
Jeff

Java sembra non amare i puntatori tanto quanto te.
Mateen Ulhaq,

4

ALTER

Quando ho imparato COBOL, l'istruzione ALTER faceva ancora parte dello standard. In breve, questa affermazione consentirebbe di modificare le chiamate di procedura durante il runtime.

Il pericolo era che si potesse inserire questa affermazione in una sezione oscura del codice a cui si accedeva raramente e che poteva potenzialmente cambiare completamente il flusso del resto del programma. Con più istruzioni ALTER potresti rendere quasi impossibile sapere cosa stava facendo il tuo programma in qualsiasi momento.

Il mio istruttore universitario, con molta enfasi, dichiarò che se avesse mai visto quella dichiarazione in uno dei nostri programmi ci avrebbe automaticamente bocciato.


Ha comunque buoni casi d'uso: mozziconi o memoizzazione. Invece di scrivere v() { if (not alreadyCalculatedResult) { result = long(operation); alreadyCalculatedResult = true; } result; }diciv() { result = long(operation); v = () => result; result; }
configuratore

4

Il peggior peccato di un linguaggio di programmazione non è stato ben definito. Un caso che ricordo è C ++, che, nelle sue origini:

  1. Era così mal definito che non era possibile ottenere un programma da compilare ed eseguire seguendo i libri o gli esempi.
  2. Una volta ottimizzato il programma per la compilazione e l'esecuzione in un compilatore e sistema operativo, dovresti ricominciare da capo se hai cambiato compilatore o piattaforma.

Come ricordo, ci sono voluti circa un decennio per definire C ++ abbastanza bene da renderlo affidabile dal punto di vista professionale come C. È qualcosa che non dovrebbe mai più accadere.

Qualcos'altro che considero un peccato (dovrebbe andare in una risposta diversa?) È avere più di un modo "migliore" per svolgere un compito comune. È il caso di (di nuovo) C ++, Perl e Ruby.


1
Non vedo come evitare la cattiva definizione in un linguaggio in evoluzione. O, del resto, in un linguaggio prestabilito in cui il designer originale ha mancato alcuni punti importanti (come Pascal).
David Thornley,

@David Thornley La buona definizione è ben definita. Resistenza agli errori, la maggior parte dei progettisti di linguaggi di programmazione lo capiscono fin dall'inizio. Gli strumenti possono verificare che una grammatica non sia ambigua quando è completa (C ++ richiede almeno tre grammatiche) e la semantica dovrebbe essere specificata per le implementazioni standard.
Apalala,

Sono d'accordo che sono possibili lingue ben definite, ma ciò non accadrà in un linguaggio in evoluzione come il C ++ pre-standard. L'alternativa è che ogni lingua sia stata accuratamente progettata prima del rilascio e non è necessariamente il modo di ottenere le lingue migliori. Direi che la maggior parte dei progettisti di linguaggi di programmazione si sbagliano all'inizio, poiché la progettazione del linguaggio è estremamente complicata.
David Thornley,

Immagino di avere problemi a capire cosa intendi per "ben definito". Ti lamenti che diversi compilatori C ++ non abbiano effettivamente compilato la stessa lingua?
Sean McMillan,

3

Le classi in C ++ sono una sorta di modello di progettazione forzata nel linguaggio.

Non c'è praticamente alcuna differenza in fase di runtime tra una struttura e una classe, ed è così confuso capire qual è il vero vantaggio di programmazione reale di "nascondere le informazioni" che voglio metterlo lì.

Sto per essere sottovalutato, ma i compilatori C ++ sono così difficili da scrivere che questa lingua sembra un mostro.


2
Il nascondere le informazioni è importante perché ti consente di nascondere dettagli specifici dell'implementazione, che probabilmente cambieranno, dalle parti accessibili dell'API ("UI" dell'API), rendendo così le modifiche al programma diventano più facili e meno dolorose.
Anto

1
L'interfaccia utente dell'API ... no, sul serio, non la compro.
jokoon

3
Questa differenza non è la parte più disgustosa del C ++, nemmeno vicina. L'unica differenza è un modificatore di accesso predefinito (pubblico per le strutture, privato per le classi). Il C ++ è un linguaggio orribile e mostruoso, ma certamente non in questa parte.
Logica SK

sk-logic: beh, potrei dire che gli orrori iniziano da lì.
jokoon

2
Nascondere le informazioni è buono; puoi trovare discussioni di tutto questo. L'unico libro di software di rilievo a cui riesco a pensare che era contro di esso era "The Mythical Man-Month" di Brooks, e in seguito lo considerò il più grande errore nel libro. Se non capisci i vantaggi, non sei davvero qualificato per esprimere il tuo giudizio.
David Thornley,

3

Anche se ogni lingua ha i suoi difetti, nessuno è fastidioso una volta che li conosci. Tranne questa coppia:

Sintassi complessa accoppiata a API complesse

Ciò è particolarmente vero per un linguaggio come Objective-C. Non solo la sintassi è straordinariamente complessa, ma l'API utilizza nomi di funzioni come:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath

Sono tutto per essere esplicito e non ambiguo, ma questo è ridicolo. Ogni volta che mi siedo con xcode, mi sento come un n00b, e questo è davvero frustrante.


La sintassi Objective-C è estremamente complessa? Non hai visto C ++? E il nome del metodo attuale esiste tableView:cellForRowAtIndexPath:, che è molto descrittivo secondo me.
bendaggio

Impara a digitare.
Finnw,

2
  1. caratteri firmati in C - un abominio inventato per consentire ai matematici di avere grandi collezioni di piccoli oggetti
  2. Usare case per trasportare contenuti semantici, ancora una volta per i matematici, che non hanno bisogno di parlare e non hanno mai abbastanza spazio per le loro formule
  3. Assegna / Assegna in modo semplice rispetto a Assegna in dialetti di base - nessun matematico coinvolto qui penso

Mi sono perso per quanto riguarda il tuo commento sui personaggi firmati, potresti spiegarmi?
Winston Ewert,

1
Per un essere umano, il concetto di caratteri (non) firmati e la necessità di dire al compilatore di usare caratteri non firmati come default è sicuramente altrettanto folle come per un matematico l'affermazione che 2! = 2, perché il secondo 2 è maiuscolo, grassetto o corsivo.
Ekkehard.Horner

5
Il problema è che C confonde il concetto di "char" (cioè parte di una stringa di testo) e "byte" (cioè, (u)int_least8_t). La firma ha perfettamente senso per i piccoli numeri interi, ma non ha alcun senso per i personaggi.
dan04

@ dan04: sono d'accordo, vorrei che avessero tipi diversi per numeri interi piccoli (con e senza segno), byte e carattere. Quando spieghi ai neofiti che per manipolare la memoria grezza devono lanciare un char*... come una C-String, diventano davvero confusi.
Matthieu M.

C # ha questo diritto con separati sbyte, bytee chartipi.
dan04
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.