すべてのプログラミング言語は、他のほとんどの(すべての)ものと同様に、単一の言語では完璧ではないという単純な理由で、設計上の欠陥を抱えています。それはさておき、プログラマーとしてのあなたの歴史を通して、プログラミング言語のどのデザインの欠陥があなたを最も悩ませてきましたか?
特定の目的のために設計されていないという理由だけで言語が「悪い」場合は、設計上の欠陥ではありませんが、設計のfeatureなので、そのような言語の不快な点を挙げないでください。言語が設計された目的に適さない場合、それはもちろん設計の欠陥です。実装に固有のものや、内部的なものも考慮されません。
私の大きな不満の1つは、C派生言語のswitch
ケースがbreak
の使用を忘れた場合にデフォルトで次のケースに陥る方法です。これは非常に低レベルのコード(例: Duff's Device )で役立つことを理解していますが、通常、アプリケーションレベルのコードには不適切であり、コーディングエラーの一般的な原因です。
Javaの詳細について初めて読んだときの1995年頃、switch
ステートメントについての部分にたどり着いたとき、彼らがデフォルトのフォールスルー動作を維持しました。これにより、switch
が別の名前の栄光のgoto
になります。
割り当てに=
を使用したり、C派生言語での同等性テストに==
を使用したりするのは好きではありません。混乱やエラーの可能性が高すぎます。また、JavaScriptで===
を始めないでください。
割り当てについては:=
であり、等価性テストについては=
であった方がよいでしょう。セマンティクスは現在とまったく同じである可能性があり、代入は値も生成する式です。
additionとstring concatenationの両方のJavascriptで+
を選択するのはひどい間違いでした。値は型指定されていないため、これは、各オペランドの正確な内容に応じて、+
が追加または連結するかどうかを決定するビザンチン規則につながります。
最初は、文字列連結用の$
などの完全に新しい演算子を導入するのは簡単でした。
JavaScriptのデフォルトは主要な問題にグローバルであり、JSLintなどを使用しない場合はバグの原因になることがよくあります
CおよびC++のプリプロセッサは大規模な処理であり、ふるいのようにリークする抽象化を作成し、ラットの#ifdef
ステートメントのネストを介してスパゲッティコードを奨励し、その制限を回避するためにひどく読めないALL_CAPS
名を必要とします。これらの問題の原因は、構文レベルまたは意味レベルではなくテキストレベルで動作することです。さまざまなユースケースで、実際の言語機能に置き換える必要があります。ここにいくつかの例がありますが、確かにこれらの一部はC++、C99または非公式で解決されていますが、デファクト標準拡張機能です。
#include
は実際のモジュールシステムに置き換える必要があります。
インライン関数とテンプレート/ジェネリックは、関数呼び出しのほとんどのユースケースを置き換えることができます。
ある種のマニフェスト/コンパイル時定数機能は、そのような定数を宣言するために使用できます。 Dの列挙型の拡張 ここでうまく機能します。
実際の構文ツリーレベルのマクロは、さまざまな使用例を解決できます。
String mixins は、コードインジェクションの使用例に使用できます。
static if
またはversion
ステートメントは、条件付きコンパイルに使用できます。
何百もの言語で何百もの間違いをリストすることもできますが、IMOは言語設計の観点からは有用な演習ではありません。
どうして?
なぜなら、ある言語での間違いは別の言語での間違いではないからです。例えば:
そこにはある学ぶべき教訓がありますが、その教訓が明確になることはめったにありません。それらを理解するには、技術的なトレードオフと歴史的背景を理解する必要があります。 (たとえば、面倒なJavaジェネリックの実装は、下位互換性を維持するためのオーバーライドビジネス要件の結果です。)
IMO、新しい言語の設計に真剣に取り組んでいる場合は、実際にseさまざまな既存の言語を(そして歴史的な言語を調べて)...そして、間違いを自分で決める必要があります。また、これらの言語はそれぞれ、特定のニーズを満たすために特定の歴史的文脈で設計されたことを覚えておく必要があります。
学ぶべき一般的な教訓がある場合、それらは「メタ」レベルにあります。
[〜#〜] c [〜#〜]およびC++:意味のないすべての整数型何でも。
特にchar
。テキストですか、それとも小さな整数ですか?テキストの場合、「ANSI」文字ですか、それともUTF-8コード単位ですか?整数の場合、符号付きですか符号なしですか?
int
は「ネイティブ」サイズの整数であることが意図されていましたが、64ビットシステムではそうではありません。
long
はint
より大きくても、大きくなくてもかまいません。ポインタのサイズである場合とそうでない場合があります。それが32ビットであるか64ビットであるかは、コンパイラー作成者の側でのほとんど任意の決定です。
間違いなく1970年代の言語。 Unicodeより前。 64ビットコンピュータの前。
null
。
その発明者であるトニー・ホーアはそれを 「10億ドルの間違い」 と呼んでいます。
これは60年代にALGOLで導入され、現在一般的に使用されているプログラミング言語のほとんどに存在しています。
OCamlやHaskellなどの言語で使用されるより良い代替手段は maybe です。一般的な考え方は、オブジェクト参照は、そうである可能性があるという明示的な指示がない限り、null /空/存在しないことはできないということです。
(トニーは謙虚で素晴らしいですが、ほとんど誰もが同じ過ちを犯したと思います、そしてたまたま彼はたまたま最初でした。)
PHPを設計した人々は、通常のキーボードを使用していません。彼らは自分が何をしているのか理解しているはずだったので、彼らはcolemakキーボードさえ使用していません。
Who::in::their::right::mind::would::do::this()
? _::
_演算子では、Shiftキーを押しながら2つのキーを押す必要があります。なんて無駄なエネルギーだ。
しかし->これ->は->ではない->はるかに->良い。また、2つの記号の間にシフトがある3つのキーを押す必要があります。
_$last = $we.$have.$the.$dumb.'$'.$character
_。ドル記号は途方もない回数使用され、キーボードの最上部までの賞の延長とシフトキーの押下が必要です。
PHPで、入力がはるかに速いキーを使用できるように設計できなかったのはなぜですか?we.do.this()
を使用できない、または変数を1つのキーを押すだけのキーで始めることができないのはなぜですか。 -またはまったく(JavaScript)、すべての変数を事前定義します(とにかくE_STRICTのために行う必要があるように)!
私は遅いタイピストではありませんが、これは設計の選択が不十分です。
asp.net内でのデスクトップにインスパイアされたformsの使用。
それは常にファッジを感じ、ウェブが実際に機能する方法または方法に干渉しました。ありがたいことにasp.net-mvcは同じように影響を受けませんが、Rubyなど)のおかげです。
私にとっては、[〜#〜] php [〜#〜]の標準ライブラリでは、命名規則と引数の順序付け規則が完全に欠如しています。
[〜#〜] jass [〜#〜]は、参照されたオブジェクトが解放された後に参照を無効にする必要があります/削除(または参照がリークして数バイトのメモリが失われる)はより深刻ですが、JASSは単一目的言語であるため、それほど重要ではありません。
私が直面する最大の設計上の欠陥は、pythonがpython 3.xのように設計されていなかったことです。
Array decay Cおよびその結果のC++。
Javaのプリミティブ型。
それらはすべてがJava.lang.Object
の子孫であるという原則を破り、理論的な観点からは言語仕様がさらに複雑になり、実用的な観点からはコレクションの使用が非常に退屈になります。
オートボクシングは実用的な欠点を軽減するのに役立ちましたが、仕様をさらに複雑にし、大きな脂肪のバナナスキンを導入することを犠牲にして、単純な算術演算のように見えるものからnullポインター例外を取得できるようになりました。
私はPerlを最もよく知っているので、それを選びます。
Perlは多くのアイデアを試しました。一部は良かった。いくつかは悪かった。一部はオリジナルであり、正当な理由で広くコピーされていません。
1つはcontextの考え方です。すべての関数呼び出しはリストまたはスカラーコンテキストで行われ、コンテキストごとにまったく異なる処理を実行できます。 http://use.Perl.org/~btilly/journal/36756 で指摘したように、これはすべてのAPIを複雑にし、Perlコードの微妙な設計上の問題を頻繁に引き起こします。
次は、構文とデータ型を完全に結び付けるアイデアです。これにより、タイが発明され、オブジェクトが他のデータ型になりすますことができます。 (オーバーロードを使用しても同じ効果を得ることができますが、Perlではtieがより一般的なアプローチです。)
多くの言語でよくあるもう1つのよくある間違いは、語彙ではなく動的スコープを提供することから始めることです。この設計の決定を後で元に戻すことは困難であり、長期的なイボにつながります。 Perlでのこれらのいぼの古典的な説明は http://Perl.plover.com/FAQs/Namespaces.html です。これは、Perlがour
変数とstatic
変数を追加する前に作成されたことに注意してください。
静的型付けと動的型付けについて、人々は合法的に意見が分かれています。個人的には動的タイピングが好きです。ただし、タイプミスを捕まえるのに十分な構造を持つことが重要です。 Perl 5はstrictでこれをうまく行います。しかし、Perl 1-4はこれを間違っていました。他のいくつかの言語には、strictと同じことを行うlintチェッカーがあります。 lintチェックを強制することに長けている限り、それは許容されます。
もっと悪いアイディア(たくさん)を探しているなら、PHPを学び、その歴史を調べてください。私のお気に入りの過去の過ち(これは非常に多くのセキュリティホールにつながるため、ずっと前に修正されました)がデフォルトでした。フォームパラメータを渡すことで誰でも任意の変数を設定できるようにしますが、それだけが間違いではありません。
私はFORTRANと空白の無感覚に戻ります。
それは仕様に浸透しました。 END
カードは、7から72桁目に「E」、「N」、「D」の順であり、他の非ブランクではないカードとして定義する必要があり、適切な列に「END」とそれ以外のものはありません。
構文の混乱を招きやすくなりました。 DO 100 I = 1, 10
はループ制御ステートメントでしたが、DO 100 I = 1. 10
は値1.1をDO10I
という変数に割り当てたステートメントです。 (変数は宣言なしで作成でき、最初の文字に応じてその型がこれに貢献しました。)他の言語とは異なり、トークンを区切るためにスペースを使用して曖昧さをなくす方法はありませんでした。
また、他の人が本当に混乱するコードを書くこともできました。 FORTRANのこの機能が二度と複製されなかった理由はいくつかあります。
コードブロックとオブジェクトリテラルのJavaScriptのあいまいさ。
{a:b}
コードブロックの場合、a
はラベル、b
は式です。または、値a
を持つ属性b
でオブジェクトを定義することもできます
BASICの最大の問題の1つは、初期の環境を超えて言語を拡張するための明確に定義された方法がないことであり、完全に互換性のない実装の束につながります(そして、標準化におけるほぼ無関係な事後の試み)。
ほとんどすべての言語は、クレイジーなプログラマーによって一般的な目的で使用されるようになります。クレイジーなアイデアが浮上した場合に備えて、最初にその汎用的な使用法を計画することをお勧めします。
私はDSL(ドメイン固有の言語)を信じており、言語で私が重視することの1つは、DSLをその上に定義できるかどうかです。
LISPにはマクロがあります。私もそうですが、ほとんどの人はこれを良いことだと考えています。
CとC++にはマクロがあります-人々はそれらについて不平を言いますが、DSLを定義するためにそれらを使用することができました。
Javaではそれらは省略されました(したがってC#では)、そしてそれらの欠如は美徳であると宣言されました。確かにそれはあなたにインテリセンスを持たせますが、私にとってそれは単なるoeuvreです。 DSLを行うには、手動で拡張する必要があります。それは苦痛であり、それは私がたくさんのたくさんのことをたくさんのコードで行うことができるにもかかわらず、私を悪いプログラマーのように見せます。
ステートメント、それらがあるすべての言語で。彼らはあなたが表現で行うことができないことを何もせず、あなたが多くのことをするのを妨げます。 ?:
三項演算子の存在は、それらを回避しようとする必要があることの1つの例にすぎません。 JavaScriptでは、これらは特に迷惑です。
// 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')
私にとって、Cから派生したすべての言語を悩ませているのは設計上の問題です。つまり、「 dangling else 」です。この文法上の問題はC++で解決されているはずですが、JavaおよびC#に引き継がれました。
これまでのすべての答えは、多くの主流言語の単一の失敗を指摘していると思います:
下位互換性に影響を与えずにコア言語を変更する方法はありません。
これが解決されれば、他のすべての不満はほとんど解決できます。
編集。
これは、さまざまな名前空間を持つことでライブラリで解決でき、言語のほとんどのコアで同様のことを行うことが考えられますが、これは、複数のコンパイラ/インタープリタをサポートする必要があることを意味する場合があります。
結局、完全に満足できる方法でそれを解決する方法がわからないと思いますが、それはソリューションが存在しない、またはこれ以上実行できないことを意味するものではありません
Javaのサイレント整数 算術オーバーフロー
私は自分を解放する気になっているように感じますが、C++で参照によりプレーンな古いデータ型を渡す機能が本当に嫌いです。参照によって複雑な型を渡すことができるのは少しだけ嫌です。関数を見ている場合:
void foo()
{
int a = 8;
bar(a);
}
呼び出し元からは、完全に異なるファイルで定義されているbar
が次のようであることを伝える方法はありません。
void bar(int& a)
{
a++;
}
このようなことをするのは悪いソフトウェア設計であり、言語のせいではないかもしれないと主張する人もいるかもしれませんが、言語が最初からこれを可能にしているのが好きではありません。ポインターの使用と呼び出し
bar(&a);
はるかに読みやすいです。
プログラミング言語の最悪の罪は明確に定義されていません。私が覚えているケースはC++で、その起源は次のとおりです。
私が覚えているように、Cと同じくらい専門的に信頼できるようにするためにC++を十分に定義するには 10年ほどかかりました です。
私が罪と考える別の何か(それは別の答えに行くべきでしょうか?)は、いくつかの一般的なタスクを実行するための複数の「最良の」方法を持っています。これは(再び)C++、Perl、Rubyの場合です。
私がCOBOLを学んだとき、 ALTERステートメント はまだ標準の一部でした。簡単に言うと、このステートメントを使用すると、実行時にプロシージャコールを変更できます。
危険なのは、めったにアクセスされないコードのあいまいなセクションにこのステートメントを置くことができ、残りのプログラムのフローを完全に変更する可能性があることでした。複数のALTERステートメントを使用すると、任意の時点でプログラムが実行していたことを知ることがほぼ不可能になる可能性があります。
私の大学の講師は、非常に強調して、もし彼が私たちのプログラムのいずれかでその発言を見たら、自動的に私たちをばかにするだろうと述べました。
C++のクラスは、言語のある種の強制設計パターンです。
実行時に構造体とクラスの間に実質的な違いはなく、「情報を隠す」ことの本当のプログラミングの利点が何であるかを理解するのは非常に混乱します。
私はそのために反対票を投じるつもりですが、とにかく、C++コンパイラーはこの言語を書くのがとても難しいので、怪物のように感じます。
すべての言語には欠点がありますが、一度理解すれば、厄介なものはありません。このペアを除いて:
複雑な構文と冗長なAPIの組み合わせ
これは特にObjective-Cのような言語に当てはまります。構文が非常に複雑であるだけでなく、APIは次のような関数名を使用しています。
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
私はすべて明示的で明確であることに賛成ですが、これはばかげています。 xcodeを使用するたびに、私はn00bのように感じ、それは本当にイライラさせられます。
Perlのリストのフラット化...逆に、それはまた、その最高の機能の1つでもあります。
古いUltima Online/sphereサーバースクリプトでは、ゲーム自体が明らかにそれらを使用していましたが、分割なしまたは小数点がありました。あなたはできませんやや粗雑な除算関数を作成しますが、かろうじて行います。
1つの欠陥が言語全体を非常に煩雑にしたという、どれだけの列車事故を言うのは困難です。
鉱山は、UMSI仕様言語、ANSI Fortranに翻訳されたマクロ言語でなければなりません。ブランクのUSLの使用は恐ろしいものでした。
USLでは、名前に空白を含めることができます。したがって、「LASTTANGO」の代わりに、マクロに「LAST TANGO」という名前を付けることができます。ただし、これは「LAST」というマクロの後に「TANGO」という名前の別のマクロが続くことを意味する場合もあります。 「LAST TANGO IN PARIS」のようなコードを読み、組み合わせの可能性は恐ろしいです。
USLは、補助コードを示すためにbegin/endまたは{}を使用せず、スペースを使用しました。 IFステートメントに続いて、IFステートメントよりもインデントされたすべての行は、そのIFを条件としました。簡単に聞こえますか?しかし、いくつかのページで条件文を追跡してみてください。 ELSEを正しいインデントで追加してみてください。
USLは1980年頃に米国政府機関で生まれ、亡くなりました。
非Javaのような言語で。 「GOTO」またはジャンプのコンセプト。非線形の方法でコードを飛び回ることができることは、おそらくどんな記述言語でも最も不合理な機能です。
これは最も誤用され、無関係な概念でした。これらの1つを300行の関数で表示すると、スパゲッティの料理レッスンに参加していることがわかります。例外はエラー処理です。これは、ジャンプの概念の唯一の許容される使用法です
それは現代の優れたプログラミング慣行を壊します。 Gotoは、エラートラップの目的でのみ使用できます。ループを終了したり、コードをスキップしたりするための怠惰な方法ではありません。
コードの記述は、読みやすさを重視したアートフォームです。可読性の多くの側面には、線形実行があります。エントリごとに1つの出口、関数ごとに1つの出口があり、ページを下に移動する必要があります。ジャンプやジャンプは行わないでください。
これにより、コードが読みやすくなるだけでなく、その性質上、より高品質のコードを記述できます。あるハックは別のものを生みます。 gotoが誤用されると、関数から複数のexitステートメントも取得されることが一般にわかります。テストのための条件とロジックのトレースは非常に困難になり、生成するコードの堅牢性がすぐに低下します。
後藤のものが永久に追放され、それらが何年も前にアセンブリのコーディングで使用されたことを願っています。
配列、リスト、辞書をPHPの「連想配列」と呼ばれる1つのabominationに詰め込みます。
Cでは、C++、Java、およびC#によって継承されます。
シンボルテーブルを作成せずに言語を解析することはできません。このようなコードのため:
Foo Bar();
タイプBar
の変数Foo
を宣言してデフォルトのコンストラクターを呼び出すか、Bar
を返し、引数を取らない関数Foo
を宣言することができます。 (とにかく、C++では、他にも同様の欠陥があります。)
つまり、シンボルテーブルを作成せずにこれらの言語を解析することはできないため、分析ツールを作成するのははるかに難しくなります。
(JavaScriptとGoは、この問題を回避するために管理しています。)
M4マクロ言語にはループ構造がありません。代わりに、ドキュメントでは、再帰を使用して独自に作成することを推奨しています。そのため、私はこの言語に煩わされることはほとんどありませんでした。
おそらく最大のデザインの欠陥ではなく、それでも私の意見では欠陥...
変数のコンテキストでのキーワードfinalはJavaです。
代わりにnon-final/mutable/rassignableキーワードがあったらいいのに変数への参照を変更できることを示します。
特にマルチスレッドアプリケーションを作成している場合は、すべての場合にキーワードfinalを自由に使用することをお勧めします。
私はこのアドバイスに従い、できる限り最後のキーワードを使用します。変数が非最終であることを必要とすることは非常にまれです。これは私のソースコードをより雑然とし、私が本当に必要以上にタイプすることを余儀なくさせます。
多くのプログラミング言語でよく見られる最大の欠点は、「ブートストラップ」できないことです。多くの場合、その言語自体だけを使用して言語とそのライブラリを実装することは不可能または実用的ではありません。
これが事実である場合、言語の設計者(および実装者)は、言語を本当に興味深く、やりがいのあるタスクに役立てるという難しい部分をスキップしています。
C++は、オブジェクトを参照型ではなく値型にすることで、オブジェクト指向プログラミングを正気に実装する機会を台無しにします。 (継承とポリモーフィズムは、静的に割り当てられた固定サイズの値渡しのデータ構造と混同しないでください。)
Java、PHP、C#、Delphi、Vala:「オブジェクトへのポインタ」と「プレーンオブジェクト」の混在、通常は「参照」と呼ばれるもの。 C++およびObject Pascalでは、ポインター構文を使用して、オブジェクトを静的変数として、オブジェクトを動的変数として作成できます。
例(C++):
x = new XClass();
x->y = "something";
if (x == null) {
x->doSomething();
}
例(Java/C#):
x = new XClass();
x.y = "something";
if (x == null) {
x.doSomething();
}
C++で嫌いなことが2つあります。
Boolへの暗黙的な変換(これは、operator int() const {...}
などの型の変換演算子を実装できるという事実によってさらに悪化します)
デフォルトのパラメーターです。はい、新しいものを追加するときにインターフェースの下位互換性を支援しますが、オーバーロードも行うので、なぜこれを行うのですか?
2つを組み合わせて、災害のレシピを用意します。
ColdfusionのネストされたCFLoop動作。
SQLの入力変数の配列処理の欠如。データベースベンダがこれをサポートする機能を追加した場合でも(SQL Serverを参照)、設計が不十分で使いにくい場合があります。
最近、ほとんどすべての言語(ほとんどのSQL実装を含む)が使用している多くの日付の書式設定および操作関数がJavascriptで省略されているため、私は不満の原因になっています。
VBのオプションのパラメーター。コードに機能を追加するのが非常に簡単で、オプションのパラメーターとして追加する場合、次に別のパラメーターとして追加するため、最初のコードが作成された後に追加された新しいケースでのみ使用されるこれらのすべてのパラメーターになり、おそらく古いコードではまったく呼び出されません。
ありがたいことに、これはC#に切り替えてオーバーロードを使用することで解決されました。
PHP 5.3にgoto
演算子を実装する決定。
以前のバージョンでは実装されなかった不適切なプログラミング方法を奨励する理由は何でしょうか?
Delphi/Pascal言語では、連結を使用しないと複数行の文字列を使用できません。
RoboCom、そのアセンブリ言語 ビット単位の演算が欠けている 。
RoboComは、学習とエンターテインメント以外の真の価値を持つ生産的な言語としてはほとんどカウントされませんが、仮想ロボットをプログラミングしてコードバトルに参加するゲームです。プログラマは、相手がそうする前に、クロックサイクルを最大限に活用して移動する必要があります。
言語が設計されたものに対して不適切である場合、それはもちろん設計の欠陥です。
特に、ゲームの目標が最適化による排除である場合、言語がビット単位の操作に欠けるのはかなり苛立たせました。私の本では、ビット単位の演算を使用して多くの最適化を行うことができるため、これは実際の設計上の欠陥です。
もう少し役立つものを提供できたらよかったのに。 :)