1. 不正なコードはどのように定義されますか?
! KissyUI は、淘宝網のフロントエンド プロジェクト Kissy のグループです。会社のイントラネットで私の「悪いコードを読むシリーズ」を見た後、Long Zang はグループ内で次のように尋ねました。
そうです、悪いコードとは具体的に何ですか?これは、別のネチズンが gtalk で私に尋ねた質問を思い出します。彼は、3 つの条件 a、b、c がすべて真の場合は偽である必要があり、すべてが偽の場合は偽である必要があると判断します。
次に、KissyUI グループの学生は多くの答えを出しました:
[javascript] view plaincopy
- // 1. サークルセンター
- if( a&&b&&c | | !a&&! b&&!c) {
- return false
- }
- // 2. Long Zang
- (a ^ b) & c
- // 3. Yu Gong (gtalk にあげた質問者) の答え
- (a xor b ) or (a xor c)
- // 4. 質問者自身の考え
- (a + b + c) % 3
- // 5. Yunqian による回答 4 の改良版
- (!!a+ !!b+! !c)%n
- // 6. バチ
- a ? (b?c:b) : (b?!b:!c)
- // 7. 呉英傑
- (a ! = b | ! v){
- return false;
- }else if(v){
- return false;
- }else{
- return true;
-
en...上記の包括的な回答の有効性。なぜなら、Long Zang が後に強調したように、「悪いコードとは何なのかについて議論するつもりのようです。実際、どうすれば悪いコードを書くことができるのでしょうか?」元の質問者のトリックを含む、さまざまな奇妙なコードが上に表示されます: -
-
-
- [javascript]
view plaincopy -
// 4. 質問者自身の考え
(a + b + c) % 3
この問題は js で発生するため、弱い型の問題があります。つまり、a、b、c は整数または文字列などである可能性があるため、(a+b+c)%3 道路はそうではありません。利用可能なので、
-
-
- 路
-
回答答 答 答
(!! A+!! B+! !c)%n
- 2. 問題の一般化と解決: 通常レベル
- 変更した場合上記の質問:
- - a、b、c の 3 つの条件ではない場合、2 つ以上の条件はどうですか?
- - a、b、c 自体は必ずしもブール値ではないことを強調するとどうなるでしょうか?
- この問題の基本的な抽象化は次のとおりです:
-
-
[c-sharp]- view plaincopy
-
-
- //v0、任意の数のオペランドの xor を求める
- functione_xor() { .. .この e_xor() の場合、コードを記述する最も直接的な方法は次のとおりです:
- // v1、すべてのパラメーターをスキャンし、異なる場合は true を返し、すべて同じである場合は false を返します。
function e_xor() {
var args=arguments, argn=args.length;
🎜 args[0] = !args[0]; 🎜 🎜 for (var i=1; i
次に、引数が配列なので、array メソッドを使用できるでしょうか?実際、一部の JS 環境では、arguments[x] への直接アクセスは効率が悪いと言われています。したがって、上記の v1 バージョンにはリビジョンが存在する可能性があります:
[javascript] view plaincopy
- //v1.1、v1 のリビジョン
- function e_xor() {
- args= [ ].slice.call(arguments,0), argn=args.length;
- ...
- }
この小さなコードには、splice/slice の使用が含まれています。スプライスは引数を操作するため、関数エントリで「特異な」変更が発生する可能性があり、異なるエンジンでのパフォーマンスへの影響に一貫性がなく、スライスでは 2 倍のデータ コピーが発生する可能性があります。ここでスライス() が依然として使用されている理由は、結局のところ、これは単なる関数パラメーターであり、「非常に大きな」配列ではないため、ストレージの問題を過度に考慮する必要がないためです。
3. 問題の一般化と解決策: プロフェッショナルレベル
次に、args で取得するものは配列であるため、for ループを使用するのは実際にはそれほど現代的ではありません。フロントエンドで軽蔑されない、正しく人気のあるスタイルは次のとおりです:
[javascript] view plaincopy
- // v2、js1.6+ の array メソッドの実装を使用
- function_xor(a) {
- return ([].slice.call(arguments,1)).some(function(b) { if (!b != !a) return true });
感謝の気持ちを表すために、js1.6+ の新機能についてあまり知らない一部の学生が、v2 バージョンについて説明します。次のコードは、上記の実装を分解したものです。 plaincopy -
// v2.1 、v2 の詳細な分解
function e_xor(a) {
var args = [].slice.call(arguments,1);
var callback = function(b) ) {
if (!b != ! a) return true
} return args.some(callback); -
} -
-
some() このメソッドは、配列 args の各要素をパラメータとして渡します。 b をコールバック関数に渡します。 some() には、元の要件と一致する機能があります。 -
-
- callback() が true を返す場合、some() は引数の列挙を中断し、true 値を返します。それ以外の場合、 -
- 列挙するときすべてを返します。要素が含まれており、callback() は true を返さず、some() は false 値を返します。 -
次に、e_xor() の v2 バージョンを読んでください。理解できましたか? -
もちろん、!a 操作を減らすためだけに、v2 バージョンに次のリビジョンを適用することもできます:
[javascript]
view plaincopy
//v2.2, for v2 の最適化による !a 操作の数を減らす
function e_xor(a) {
return (a=!a, [].slice.call(arguments,1)).some(function(b) { if (!b != a) return true });
このコード行では、連続操作が使用されます。
( a=! a, [].slice.call(arguments,1)) -
-
そして、連続演算は、slice() の後の配列である最後の部分式の値を返します。この書き方は主にコードを「1つの式」に抑えることを目的としています。 -
-
4. 問題の一般化と解決策: 初心者向けのレベル
さて、今から v3 バージョンを書き始めます。なぜ? v2 バージョンはまだ十分にクールではないため、v2 バージョンでは Array.some() が使用されています。js1.6 で拡張されたこの機能はあまり「機能的」ではなく、オブジェクト指向の痕跡がいくつかあります。関数型言語の熱心なファンとして、私は「配列の列挙」のような問題に対する最も一般的な解決策は再帰であると信じています。
なぜですか? Erlang のような純粋な関数型言語では、Array.some() というアイデアは思いつきません。もちろん、そのようなメソッドはありますが、「より純粋な」観点から見ると、自分で記述する必要があります。ふふ。この種の「純粋な再帰」を js で行うにはどうすればよいでしょうか?おおよそのプロトタイプは次のようになります:
[javascript] view plaincopy
- // v3、純粋に関数型の再帰スキーム フレームワーク
- function e_xor(a, b) { ..このフレームワークでは、e_xor() には無数のパラメーターがあると想定していますが、毎回 a と b のみを処理します。a と b が等しい場合、それらのいずれか 1 つを後続の n-2 と照合します。パラメータは再帰的に比較されます。 「後続の n-2 個のパラメータの再帰的処理」を実現するには、関数型言語の重要な概念である連続性/連続性 (連続) を借用する必要があります。この Dongdong Yueying はかつて特別なトピックについて話していました。それは次のとおりです:
http://bbs.51js.com/viewthread.php?tid=85325
簡単に言えば、継続とは関数に対して継続的にコールバックを実行することです。パラメーター 。この機能は、新しい関数型言語パラダイムでサポートされています。この記事のこの例では、分析するために別のバージョンを作成しました。私はこれを tail() メソッドと呼んでいます。これは、関数パラメーターの末尾を指定することを意味し、関数 Function のプロトタイプ メソッドとして設計されています。
[javascript] view plaincopy
Function.prototype.tail = function() { return this.apply(this, [].slice.call(arguments,0 ).concat( [].slice.call(this.arguments, this.length))); - }
-
- この tail() メソッドの興味深い点に注目してください。このメソッドでは this.length が使用されています。 JavaScript の関数には 2 つの長さの値があります。1 つは foo.length で、宣言時の foo 関数の仮パラメータの数を示します。もう 1 つは、関数の呼び出し時に渡される実際のパラメータを示します。番号。つまり、関数 foo() の場合:
[javascript] view plaincopy
function foo(a, b) { alert([arguments.length, argument.callee] .length]); - }
- foo(x,y,z);
-
- 2 回目の呼び出しでは [ 3, 2]。いずれの場合も、宣言時のパラメータ a と b は常に 2 であるため、foo.length == argument.callee.length == 2 となります。
-
tail() メソッドに戻ります。それは意味します:
[javascript]
view plaincopy
Function.prototype.tail = function() {
return this.apply( // 関数自体を呼び出します
this, // を使用しますfunction foo 自体をこの Object として扱う [].slice.call(arguments,0) // tail 呼び出し時にすべてのパラメータを取得し、配列に変換する -
.concat( // 配列接続 -
[].slice .call (this.arguments, // この関数 foo を呼び出すときにパラメータを取得します。foo() では tail() が常に呼び出されるため、実際には最新の foo() の実際のパラメータを取得します -
this.length) // によるとfoo() が宣言されているときの仮パラメータの数、foo() 関数のパラメータの末尾をインターセプトします -
) -
); -
} -
-
それでは、この例では tail() はどのように使用されるのでしょうか? -
-
[javascript]
view plaincopy
// v3.1、tail() を使用したバージョン
function e_xor(a, b) {
if (argu ments.length == 引数.callee.length) return !a != !b; return (!a == !b ? argument.callee.tail(b) : true);Arguments.callee.length は、仮パラメータの数を決定するためにここでも使用されます。つまり、再帰の終了条件は、パラメータ a と b の 2 つだけが残っており、tail() 部分をスキャンする必要がないということです。もちろん、戻り値の 3 項式の右半分 (?:) も再帰を終了します。この場合、別の条件が見つかりました。
この例では、末尾再帰関数として e_xor() を作成しましたが、この末尾再帰は関数型スタイルの本質です。残念ながら、その最適化は js ではサポートされていません。うわぁ~~ 後でリソースをチェックして、新しい Chrome v8 がサポートしているかどうかを確認します。クラスメイト v8、まだ V5 が必要ですか? :)
5. 問題の一般化と解決: Guy の上級レベル
前のセクションで、Guy の問題解決のアイデアを見てきました。しかし、このレベルでは、多くの場合、抽象化の最初のステップが最も重要です。簡単に言えば、v3は次のとおりです
このフレームワークの抽象化自体に問題がある可能性があります。正しい理解は、「a、b は排他的論理和である」ではなく、「a は他の要素と排他的論理和である」です。したがって、v4 のフレームワーク抽象化は次のようになります。
[javascript] view plaincopy -
-
//v4、より優れた関数型フレームワーク抽象化、インターフェイスについて考える
function e_xor (a ) { .. v3では毎回bの値を後続部分に渡す必要があるため、tail()内で配列の結合concat()を行う必要があります。ただし、v4 のフレームワークを使用すると、b の値自体が後続の部分で暗黙的に保持されるため、結合する必要がありません。このように、tail() には新しい書き方が追加されました。実際、これは tail() の本来の意図に沿ったものです。スプライシング処理がある場合は、tail() ではなく foo() で処理する必要があります。 ) ) を処理します。
[javascript]
view plaincopy
-
//元の抽象的な意味に沿った tail メソッド -
Function.prototype.tail = function() {
returnこれ。 apply(this, [].slice.call(this.arguments, this.length));
}
v4 でのコードの記述はより簡単になります。
//v4.1、v3 よりも単純な実装 function e_xor(a) { -
if (arguments.length < 2) return false -
return ( !a == !arguments[1] ] ? argument.callee.tail() : true); -
} -
// 三項式を使用しない簡潔なバージョン
function e_xor(a) {
if (arguments.length < 2) return false;
if (!arguments[1] != !a) return true;
return argument.callee.tail();
問題: ガイには階級がない - いわゆる階級のない階級とは、彼がガイであることは知っているが、どこまでガイになれるかはわからないということを意味します。たとえば、v4.1 バージョンの e_xor() には、次のようなパターンがあります。
- - 実際の処理ロジックは 2 行目のみです。
- 他のすべてはフレームワークの一部であるため、tail の拡張であるプログラミング パラダイムを考えることができます。その目的は、配列で sort() メソッドを呼び出すのと同じように、tail で e_xor を呼び出すことです。 tail の意味はデータを取得することであり、新しい拡張機能の意味は配列とロジックの両方を全体として取得することです。例:
-
-
[javascript]- view plaincopy
-
-
- // 関数プロトタイプで拡張された tailed メソッドは、パラメーターの末尾を付けるために使用されます
- Function.prototype.tailed = function() {
- return function(f) { // パラメーター f を通じて関数 this を保持します。 Closure
- return function() { // tailed() の後に呼び出し可能な e_xor() 関数
- if (arguments.length < f.length+1) return false
- if (f. apply(this, argument) ) return true; // tailed() の前に関数 f を呼び出します
- return argument.callee.apply(this, [].slice.call(arguments, f.length));
- }
- }(this)
- }
tailed() の使用法は非常に簡単です:
[javascript] view plaincopy
- e_xor = function(a){
- if ( !arguments[1] ! = !a) return true;
- }.tailed();
簡単に言うと、tailed() のオペランドとして xor 関数を使用できます。その核心は、xor に似た一連の関数を公開することであり、開発者は次のプログラミング パラダイムを使用して操作を実装できます。例:
[javascript] view plaincopy
/* tiny tailed ライブラリ、v0.0.0.1 alpha (aimoo 作成) */ -
Function.proto type.tailed = ... .; -
//パラメータ a と後続のすべてのパラメータの排他的論理和 -
function xor(a) { -
if (!arguments[1] != !a) return true -
} -
// ..さらに類似したライブラリ関数 -
それでは、このいわゆる tailed ライブラリをどのように使用するのでしょうか?非常に単純なコード行: 一
[javascript] View plaincopy
// 複数のパラメータの XOR 値を求める - xor.tailed () (A, B, C , D, D, D, D, D, D, D, d ,e,f,g);
-
これで、tailed と呼ばれる半成熟したオープン ライブラリができました。いわゆる半成熟しているのは、tailed() にまだ次のコード行のような小さな欠陥があるためです。 .length +1) return false;
中央の f.length+1 の「1」は、xor によるデータの処理方法に関連する条件パラメーターです。簡単に言うと、a と argument[1] を比較する必要があるため、ここで +1 が必要になるのです。アルゴリズムが複数のオペランドを比較する必要がある場合、tailed() は汎用ではありません。したがって、正しく完全な末尾を使用すると、呼び出し元が終了条件を指定できるようになります。例:
[javascript]- view plaincopy
- // 末尾ライブラリ関数のグローバル定数としてのless_one()、およびデフォルトの閉じた条件
- //less_one が true を返すと、再帰が終了する必要があることを示します
- functionless_one(args, f) {
- if ( args.length < f.length+1) return true;
- }
- //関数プロトタイプで拡張された tailed メソッド、パラメータの末尾に使用されます
- Function.prototype.tailed = function(closed) {
- return function(f) { // 関数 this をパラメータ f を通じてクロージャに保持します
- return function() { // tailed() の後の呼び出し可能な e_xor() 関数
- if ((closed ||less_one). apply(this, [arguments,f])) return false;
- if (f.apply(this, argument)) return true; // tailed() を呼び出す前の関数 f
- callee.apply(this, [].slice.call(arguments, f.length));
- }
- }(this)
- }
使用されるメソッドは次のとおりです:
[javascript] プレーンコピーを表示
xor.tailed()(a,b,c,d,e,f,g); -
// または -
xor.tailed(less_one)(a,b ,c,d,e ,f,g); -
さまざまな操作では、less_one() を他の終了条件にすることができます。
さて、この解決策では、Guy には tailed ライブラリで十分ですか?いいえ。不倫には後がないと言われており、不倫する人によって不倫のやり方は様々です。たとえば、上記のコードには問題があることがわかります。つまり、tailed() には多くのレベルの関数クロージャがあり、呼び出し時に効率とストレージ スペースが不必要に消費されることを意味します。それで、何ができるでしょうか?例えば?あはは、パラダイム プログラミングに参加してテンプレートを作成できます:
view plaincopy
/* テンプレート フレームワークを備えた小さなテール ライブラリ、v0.0.0.1 oo 。 -
Function.prototype.templeted = function(args) { -
var buff = ['[', ,'][0]']; -
buff[1] = this.toString().replace( /_( [^_]*)_/g, function($0,$1) { return args[$1]||'_'}); -
return eval(buff.join('')); tailed() { -
var f = _execute_; -
if (_closed_(arguments, f)) return false; -
return argument.callee .apply( this, [].slice.call(arguments, f.length)); -
} -
functionless_one(args, f) { -
if (args.length < f.length+1) return true ; } -
function xor(a) { -
if (!arguments[1] != !a) return true; -
} -
e_xor = tailed.templeted({ -
close:less_one, -
実行: xor -
}) -
-
もちろん、まだまだできることはあります。たとえば、このテンプレット エンジンは非常に粗雑であり、eval() の使用方法は new Function ほど理想的ではありません。この部分については、QoBean のメタ言語処理方法を参照してください。実際、後半部分はすでにメタ言語プログラミングに近づいているためです。 -
-
-
7. 男?
何をしてるの?私たちは真実からどんどん遠ざかってしまいました。言い換えれば、私は意図的に、面白そうなサークルにみんなを誘導しましたが、徐々に真実から遠ざかっていきました。
私たちは「それほど悪くないコード」を探しているのではないでしょうか?そうであれば、a、b、c の 3 つの操作条件を判断する最良の方法はおそらく次のとおりです。 )
または、a、b、c の型を考慮すると:
[javascript] view plaincopy -
(!a!= !b || !a! =!c)
オペランドのセットを判断する状況を考慮する場合、それを配列として扱い、次のように記述します:
[javascript] view plaincopy -
function e_xor(a) {
for (var na=!a,i=1; i if (!arguments[i] != na) return true
} return false;
} -
このコードでは、最適化がある場合は、引数に JS のデフォルトのアクセス ルールを使用します。最適化がない場合は、アプリケーション環境が「引数」を要求しないためです。ここには数千件あります」または「e_xor() が非常に頻繁に呼び出されます」。需要がなければ、この領域で行った最適化は、技術的な完成度を除けば無駄な機能となり、アプリケーション環境にとっては意味がありません。 -
-
それで十分です。私たちが学んだことはアプリケーション環境では十分であり、テクニックをコードに広めないようにしてください。いわゆるテクノロジーとは、テクノロジー自体を強力にしたり完璧にするものではなく、コードの複雑さを制御し、コードを美しくする能力です。 -
-
したがって、以前「悪いコードを読む」システムで議論したとき、私は実際に 3 つのプロセスを強調しました: -
- まずビジネス ニーズを明確に考える、
- 明確で明確な呼び出しインターフェイスを設計する、
-最も単純で最短距離のコードで実装されています。
他のものはただ浮かぶ雲です。
======
注: セクション 2 からセクション 6 までのこの記事は、アーキテクチャ、フレームワーク、ライブラリなどに興味があり、言語設計、アーキテクチャの抽象化などに興味がある学生のみを対象としています。 、または基本的なプロジェクトで使用される関連テクノロジーについて議論することは歓迎されますが、一般的なアプリケーション プロジェクトで悪用してはなりません。