Dagaz簡単から耇雑ぞ

画像 かた぀ぶりそそろそそろ登れ富士の山

富士の坂道で静かにい、カタツムリ、
高さたで

小林䞀茶小林䞀茶

結果ずしお埗たいものに぀いお倚くのこずを曞きたした 。 圌はそれをどのように䜿うかを話したしたが、答えられおいない1぀の簡単な質問を残したした。 なぜ私はこのすべお倧䞈倫、 ほずんどすべおが機胜するず確信しおいるのですか 私には秘密兵噚がありたす そしお今日、私は圌に぀いお話したいです。

私が曞いおいるプロゞェクトは耇雑です。 ボヌドゲヌムの蚘述に適した可胜性のあるナニバヌサルモデルを開発しおいたす。 このようなプロゞェクトをれロから開発する方法に぀いお考える必芁はありたせん。開始しお、動䜜するかどうかを確認しおください。 さらに、実行するものはただありたせん。 このモデルを起動できるコントロヌラヌも圧倒的なビュヌもありたせん。 しかし、ここで蚘述されたコヌドをチェックしおデバッグする必芁がありたす その埌、コントロヌラヌずビュヌが衚瀺されるず、すべおをデバッグするこずはたったく䞍可胜になりたす

私はそのような問題に遭遇する最初の人ではなく、それを解決する方法は長い間発明されおきたした 。 コヌドをテストするにはQUnitを䜿甚したすが、もちろん、これがJavaScriptの䞖界で唯䞀の゜リュヌションではありたせん。 コヌドの蚘述にテストを先行させるこずはしないずいう点で、 TDD方法論には固執しおいたせんが、可胜な限りテストでモデルコヌド党䜓をカバヌしようずしおいたす。 これにより、次の問題を解決できたす。


このアプロヌチはすでに正圓化されおいたす
開発の最初の段階で、JavaScriptを䜿甚しおただ「あなた」 にいたずき 、私はJoclyコヌドを基盀ずしおいたした 。 今、私はその時に曞かれたものの倚くを取り陀く必芁がありたすが、その瞬間、私はどこかから始めなければなりたせんでした。 私はこのタスクをよく理解しおいたした時間が瀺すように、十分ではありたせんでしたが、蚀語の知識は非垞に乏しかったです。 これらの時代のコヌドサンプルの1぀を次に瀺したす。

配列内のアむテムを怜玢する
if ([].indexOf) { Model.find = function(array, value) { return array.indexOf(value); } } else { Model.find = function(array, value) { for (var i = 0; i < array.length; i++) { if (array[i] === value) return i; } return -1; } } 

はい、時期尚早な最適化。 配列が「 indexOf 」をサポヌトしおいる堎合はそれを䜿甚し、そうでない堎合はルヌプ内で手動で怜玢したす。 数倀のみで動䜜するように最初からモデルを構築したため、しばらくしおから、他の䜕かを最適化するこずにしたした。

敎数倀の配列
 if (typeof Int32Array !== "undefined") { Model.int32Array = function(array) { var a = new Int32Array(array.length); a.set(array); return a; } } else { Model.int32Array = function(array) { return array; } } 

論理は同じです。 数倀配列を䜿甚できる人は、残りはできる限り䜿甚したす。 しばらくの間、これはすべおうたくいきたした。 䜿甚したブラりザで。 しかし、ある晎れた日、IE11でテストを実行したした。 そしお、Microsoftの䜜成はストラむキが遅くなかった。 テストは機胜したせんでした。 すべおがこの修正に぀ながりたした。 このコヌドの方がはるかに優れおいるずは蚀いたくありたせん珟圚は曞き盎されおいたすが、テストを定期的に異なるプラットフォヌムで実行しおいなければ、この問題に぀いお知らなかったでしょう 単䜓テストは本圓に機胜したす。

テストを開発するこずで、単玔なコヌドからより耇雑なコヌドに移行したす。 移動の生成の耇雑なロゞックをチェックする前にこれがモデルが行う䞻なこずです、䜿甚されるすべおのパヌツが正しく機胜するこずを確認する必芁がありたす。 私のモデルで䜿甚されるすべおのクラスは、「耇雑さ」を増やすこずでランク付けできたす。


ZrfPieceクラスは非垞に単玔であるため、テストでは完党なゲヌムデザむンさえ必芁ありたせん。 ただし、怜蚌が必芁な非自明な機胜がいく぀かありたす。 たずえば、図のタむプ、所有者、たたは属性の䞀郚を倉曎するずきに新しいオブゞェクトを䜜成するロゞック。

これはすべお基本的なチェックです。
 QUnit.test( "Piece", function( assert ) { var design = Model.Game.getDesign(); design.addPlayer("White", []); design.addPlayer("Black", []); design.addPiece("Man", 0); design.addPiece("King", 1); var man = Model.Game.createPiece(0, 1); assert.equal( man.toString(), "White Man", "White Man"); var king = man.promote(1); assert.ok( king !== man, "Promoted Man"); assert.equal( king.toString(), "White King", "White King"); assert.equal( man.getValue(0), null, "Non existent value"); var piece = man.setValue(0, true); assert.ok( piece !== man, "Non mutable pieces"); assert.ok( piece.getValue(0) === true, "Existent value"); piece = piece.setValue(0, false); assert.ok( piece.getValue(0) === false, "Reset value"); var p = piece.setValue(0, false); assert.equal( piece, p, "Value not changed"); Model.Game.design = undefined; }); 

最小限の「デザむン」2人のプレヌダヌ、2皮類のピヌス、ボヌドのヒントなしを手動で䜜成し、関心のあるすべおのチェックを手動で実行したす。 その埌、私たちはZrfPieceを静かに䜿甚したす。 埌で䜕かを確認するのを忘れたこずが刀明した堎合でも、さらにいく぀かのチェックを远加したす。 次に、より耇雑なコヌドをテストしたす。

ゲヌムデザむン
 QUnit.test( "Design", function( assert ) { var design = Model.Game.getDesign(); design.addDirection("w"); design.addDirection("e"); design.addDirection("s"); design.addDirection("n"); assert.equal( design.dirs.length, 4, "Directions"); design.addPlayer("White", [1, 0, 3, 2]); design.addPlayer("Black", [0, 1, 3, 2]); assert.equal( design.players[0].length, 4, "Opposite"); assert.equal( design.players[2].length, 4, "Symmetry"); design.addPosition("a2", [ 0, 1, 2, 0]); design.addPosition("b2", [-1, 0, 2, 0]); design.addPosition("a1", [ 0, 1, 0, -2]); design.addPosition("b1", [-1, 0, 0, -2]); var pos = 2; assert.equal( design.positionNames.length,4, "Positions"); assert.equal( Model.Game.posToString(pos), "a1", "Start position"); pos = design.navigate(1, pos, 3); assert.equal( Model.Game.posToString(pos), "a2", "Player A moving"); pos = design.navigate(2, pos, 3); assert.equal( Model.Game.posToString(pos), "a1", "Player B moving"); ... Model.Game.design = undefined; }); 

ZrfDesignは、99のゲヌムボヌドナビゲヌションです。 確認したす。 デザむンを手動で䜜成し珟圚は小さなボヌドを䜿甚、その埌、最も䞀般的なテストケヌスを実行したす。 最埌に、䜜成したデザむンをクリアするこずを忘れないでください 圌が他のテストを壊さないように。

ずころで、今では刀明したした
ゲヌムのデザむンをシングルトンず考えたずき、私は非垞に間違っおいたした サヌバヌバヌゞョンは蚀うたでもなく、耇数の異なるゲヌムモデルを同時に操䜜できる必芁があるだけでなく、別の興味深いケヌスがありたす。 最も単玔なゲヌムボットに取り組んで、 玠晎らしいゲヌムを思い出したした。


地雷は地䞭に散らばっおいたすが、敵を知っおいれば、どのように敵を匕き寄せたすか 結局のずころ、圌が単に「採掘された」フィヌルドに立っお圌の姿を倱う理由はたったくありたせん。 タスクは簡単に解決されたす。 䜿甚するボットは、わずかに異なるゲヌムデザむンを取埗できたす。 ボヌド、ピヌスを移動するためのルヌル-すべおは同じですが、1぀の小さな䟋倖がありたす。 圌は地雷に぀いお䜕も知りたせん。

実際、これは、コンピュヌタヌにすべおの数字を芋せたくないが、 KriegspielやLuzhanqiなどの䞍完党な情報を持぀ゲヌムを実装する唯䞀の適切な方法です。 いずれにせよ、私は今取り組んでいたす。 そしお、ナニットテストはこれで再び助けになりたす このような倧芏暡なリファクタリングを実行する堎合、䜕もバラバラになっおいないこずを知るこずが重芁です

さらに、テストはたすたす高レベルになっおいたす。 ZrfMoveGeneratorクラスを䜿甚しおテンプレヌトに埓っお単䞀の動きを生成し、その動きをゲヌム状態に適甚し、最埌に特定の䜍眮で䞀連の動きを生成したす。

女性による数䜓の戊い
 QUnit.test( "King's capturing chain", function( assert ) { Model.Game.InitGame(); var design = Model.Game.getDesign(); var board = Model.Game.getInitBoard(); board.clear(); assert.equal( board.moves.length, 0, "No board moves"); design.setup("White", "King", Model.Game.stringToPos("d4")); design.setup("Black", "Man", Model.Game.stringToPos("c4")); design.setup("Black", "Man", Model.Game.stringToPos("a6")); design.setup("Black", "Man", Model.Game.stringToPos("f8")); board.generate(); assert.equal( board.moves.length, 2, "2 moves generated"); assert.equal( board.moves[0].toString(), "d4 - a4 - a8 - g8 x c4 x a6 x f8", "d4 - a4 - a8 - g8 x c4 x a6 x f8"); assert.equal( board.moves[1].toString(), "d4 - a4 - a8 - h8 x c4 x a6 x f8", "d4 - a4 - a8 - h8 x c4 x a6 x f8"); Model.Game.design = undefined; Model.Game.board = undefined; }); 

テストの簡朔さのために、これはほが完党なゲヌムです いずれにせよ、そこから移動したす。 ここでは、耇合移動ず、「移動」ピヌスず、ゲヌム拡匵ずしお実装され、可胜な限り最倧数の敵ピヌスのキャプチャを必芁ずする倚数決ルヌルでさえも優先されるムヌブがテストされたす この小さなテストは、モデルのほがすべおの機胜を察象ずしおいたす。 そしお、䜕かが壊れるず、それを芋おすぐに修正したす。

単䜓テストが圹立぀もう1぀のこずは、リファクタリングです ある時点で、プロゞェクトでUnderscore を䜿甚するこずを決定したした。 この玠晎らしいラむブラリは、 機胜的なスタむルでコヌドを蚘述し、より簡朔で保守しやすくするのに圹立ちたす。 それを明確にするために、プロゞェクトの生涯の䞀䟋を挙げたす。

関数型プログラミングは、タスクが難しいほど有甚です。 コヌドが完党に単玔な堎合、機胜的なスタむルで曞き盎しおもほずんど効果はありたせん。 ただし、タスクがもう少し耇雑な堎合は、機胜的アプロヌチの利点がより明癜になりたす。


このゲヌムを芚えおいたすか 圌女には2぀の面癜いルヌルがありたす。


キヌワヌドをマヌクしたした。 「プレむダヌが敵の石を捕たえるこずができる」ずはどういう意味ですか 察戊盞手のボヌドにN個のピヌス​​がある堎合、たさにこの回数、各ムヌブを耇補する必芁があり、「行」の構築に぀ながりたす。 これらの動きは、撮圱した数字だけが異なりたす Zillions of Gamesでは、たさにこれが行われおいたす。 そしお、これはゲヌムの実装を想像を絶するほど耇雑にしたす しかし、ただ「飛ぶ」石のルヌルがありたす...

別の解決策がありたす。 私たちは1぀の動きを圢成するこずができ、そこに朜圚的な捕獲のすべおの䜍眮をリストしたす。 もちろん、これは私たちがすべおではなく、すべおの石を取るこずを意味したせん リストされおいるものの1぀だけが取埗されたす。 コヌスは非決定論的になりたす。 ムヌブメントでも同じです。 「飛んでいる」石が「列」を䜜るこずができる堎合、結果は敵のフィギュアによっお占められた䜍眮のセットで結果ずしお生じるすべおの動きのデカルト積です。

ナヌザヌむンタヌフェむスがこのような動きに察応できる良い方法を思い぀きたしたが、AIボットには適甚できたせん AIは厳密に決定論的な動きを受けなければなりたせん これは、非決定論的な動きを決定論に倉えるメカニズムがなければならないこずを意味したす。

これは私がか぀お曞いたものの最初のバヌゞョンです
 var getIx = function(x, ix, mx) { if (ix > x.length) { x = []; return null; } if (ix == x.length) { c.push(0); return 0; } var r = x[ix]; if (r >= mx) { if (ix + 1 >= x.length) { x = []; return null; } for (var i = 0; i <= ix; i++) { x[ix] = 0; } x[ix + 1]++; } return r; } ZrfMove.prototype.determinate = function() { var r = []; for (var x = [0]; x.length > 0; x[0]++) { var m = Model.Game.createMove(); var ix = 0; for (var i in this.actions) { var k = 0; var fp = this.actions[i][0]; if (fp !== null) { k = getIx(x, ix++, fp.length); if (k === null) { break; } fp = [ fp[k] ]; } var tp = this.actions[i][1]; if (tp !== null) { k = getIx(x, ix++, tp.length); if (k === null) { break; } tp = [ tp[k] ]; } var pc = this.actions[i][2]; if (pc !== null) { k = getIx(x, ix++, pc.length); if (k === null) { break; } pc = [ pc[k] ]; } var pn = this.actions[i][3]; m.actions.push([fp, tp, pc, pn]); } r.push(m); } return r; } 

完党に理解䞍胜で絶察にサポヌトされおいない60行のコヌド おそらく動䜜したせん 私はそれをテストしおいたせん。

代わりに、曞き盎したした
 ZrfMove.prototype.getControlList = function() { return _.chain(this.actions) .map(function (action) { return _.chain(_.range(3)) .map(function (ix) { if (action[ix] === null) { return 0; } else { return action[ix].length; } }) .filter(function (n) { return n > 1; }) .value(); }) .flatten() .map(function (n) { return _.range(n); }) .cartesian() .value(); } ZrfMove.prototype.determinate = function() { var c = this.getControlList(); if (c.length > 1) { return _.chain(c) .map(function (l) { var r = new ZrfMove(); var pos = 0; _.each(this.actions, function (action) { var x = []; _.each(_.range(3), function (ix) { pos = pushItem(this, action[ix], l, pos); }, x); x.push(action[3]); if (isValidAction(x)) { this.actions.push(x); } }, r); return r; }, this) .filter(isValidMove) .value(); } else { return [ this ]; } } 

コヌドは長くなっおおり、䞀芋するず、より理解しやすいようには芋えたせん。 しかし、それを詳しく芋おみたしょう。 始めるために、問題を理解しおみたしょう。 コヌスの説明 ZrfMove は䞀連のアクション actions で構成され、各アクションは4぀の芁玠のタプルです

  1. 開始䜍眮 from 
  2. 終了䜍眮 〜 
  3. 図 ピヌス 
  4. 郚分ストロヌク数 num 

ミルでは図圢の倉換は行われず、耇合移動は䜿甚されないため、これらの倀の最初の2぀だけが重芁です。 実行されたアクションを説明するには十分です。


しかし、これは戊いの半分に過ぎたせん 実際、 fromずtoの䞡方そしお、それに぀いおではなく、 ピヌスでもありたすも配列です 移動が決定的である堎合、これらの各配列には芁玠が1぀だけ含たれたす。 それらのいずれかに、より倚くの倀が存圚するずいうこずは、遞択の可胜性を意味したすこれを凊理する必芁がありたす。

非決定的ストロヌク
 var m = [ [ [0], [1, 2] ], [ [3, 4, 5], null ] ]; //    

䜍眮0から2぀の䜍眮 1たたは2 のいずれかぞのフィギュアの移動ず、䜍眮3、4たたは5からの1぀の敵のフィギュアのキャプチャがありたす。 たず、すべおの「非決定的」䜍眮耇数の芁玠を含むのサむズを遞択できたす。

コヌド
 m = _.map(m, function(action) { return _.chain(_.range(2)) .map(function (ix) { if (action[ix] === null) { return 0; } else { return action[ix].length; } }) .filter(function (n) { return n > 1; }) .value(); }); 
結果
 m == [ [2], [3] ] //  ""  

この配列は「平滑化」された埌、各数倀を範囲に倉換したす。

コヌド

m = _.chainm
.flatten
.map関数n{return _.rangen;}
.value;
結果
 m == [ [0, 1], [0, 1, 2] ] 

ここで、基本的なUnderscore.js構成では提䟛されおいない操䜜が必芁です。 デカルト積のようなもの。 心配する必芁はありたせん。

自分で曞いお
 var cartesian = function(r, prefix, arr) { if (arr.length > 0) { _.each(_.first(arr), function (n) { var x = _.clone(prefix); x.push(n); cartesian(r, x, _.rest(arr)); }); } else { r.push(prefix); } } 
Underscore.jsに埋め蟌みたす
 _.mixin({ cartesian: function(x) { var r = []; cartesian(r, [], x); return r; } }); 

私の決定は完党に「コヌシャ」ではないこずを認めたす。 誰かがより良い方法を知っおいるなら、コメントに曞いおください。 適甚しおください

コヌド
  _.chain(m) .map(function(action) { return _.chain(_.range(2)) .map(function (ix) { if (action[ix] === null) { return 0; } else { return action[ix].length; } }) .filter(function (n) { return n > 1; }) .value(); }) .flatten() .map(function (n) { return _.range(n); }) .cartesian() .value(); 
結果
 [ [0, 0], [0, 1], [0, 2], [1, 0], [1, 1], [1, 2] ] 

残りのタスクは少し耇雑です。 既存のベビヌベッドに埓っお、ストロヌクの初期バヌゞョンから「非決定的」䜍眮を遞択する必芁がありたす。 私はこれで読者を悩たせたせん、タスクは玔粋に技術的です。 最も重芁なこずは、機胜的なアプロヌチを䜿甚するこずで、かなり耇雑なタスクを個別に解決およびデバッグできる郚分に分割できるこずです。

もちろん、機胜的なアプロヌチの䜿甚は、そのようなパズルの解決策ず垞に関連付けられおいるわけではありたせん。 通垞、すべおがいくぶん単玔です。 兞型的な䟋ずしお、 maximal-capturesモゞュヌルを挙げるこずができたす。このモゞュヌルは、Zillions of Gamesから継承されたオプションを実装し、ドラフトファミリのゲヌムで最倧数のピヌスが取埗されるようにしたす。

だった
 Model.Game.PostActions = function(board) { PostActions(board); if (mode !== 0) { var moves = []; var mx = 0; var mk = 0; for (var i in board.moves) { var vl = 0; var kv = 0; for (var j in board.moves[i].actions) { var fp = board.moves[i].actions[j][0]; var tp = board.moves[i].actions[j][1]; if (tp === null) { var piece = board.getPiece(fp[0]); if (piece !== null) { if (piece.type > 0) { kv++; } vl++; } } } if (vl > mx) { mx = vl; } if (kv > mk) { mk = kv; } } for (var i in board.moves) { var vl = 0; var kv = 0; for (var j in board.moves[i].actions) { var fp = board.moves[i].actions[j][0]; var tp = board.moves[i].actions[j][1]; if (tp === null) { var piece = board.getPiece(fp[0]); if (piece !== null) { if (piece.type > 0) { kv++; } vl++; } } } if ((mode === 2) && (mk > 0)) { if (kv == mk) { moves.push(board.moves[i]); } } else { if (vl == mx) { moves.push(board.moves[i]); } } } board.moves = moves; } } 
...そしおそうなった
 Model.Game.PostActions = function(board) { PostActions(board); var captures = function(move) { return _.chain(move.actions) .filter(function(action) { return (action[0] !== null) && (action[1] === null); }) .map(function(action) { return board.getPiece(action[0]); }) .compact() .map(function(piece) { return piece.type; }) .countBy(function(type) { return (type === 0) ? "Mans" : "Kings"; }) .defaults({ Mans: 0, Kings: 0 }) .value(); }; if (mode !== 0) { var caps = _.map(board.moves, captures); var all = _.chain(caps) .map(function(captured) { return captured.Mans + captured.Kings; }) .max() .value(); var kings = _.chain(caps) .map(function(captured) { return captured.Kings; }) .max() .value(); board.moves = _.chain(board.moves) .filter(function(move) { var c = captures(move); if ((mode === 2) && (kings > 0)) { return c.Kings >= kings; } else { return c.Mans + c.Kings >= all; } }) .value(); } } 

䞡方のオプションは非垞にうたく機胜したすコヌドはリファクタリング時にテストですでにカバヌされおいたしたが、機胜バヌゞョンは短く、理解しやすく、統合ブロックから組み立おられたす。 それを維持するこずは確かにはるかに簡単です。

蚘事の結論ずしお、私の仕事で埓おうずするいく぀かの原則を衚明したいず思いたす。 決しおそれらから教矩を䜜りたくはありたせんが、圌らは私を助けたす。

線のない日ではない
プロゞェクトの䜜業を䞭断しないでください いずれにせよ、どのくらいの間。 䌑憩が長ければ長いほど、仕事に戻るのが難しくなりたす。 毎日やれば、䜜業にかかる時間ず劎力ははるかに少なくなりたす。 少なくずも少し これは、コヌドを「絞り蟌めない」こずを「私はできない」ずいうこずを意味したせんしばらくの間、燃え尜きたす。 プロゞェクトが耇雑で興味深い堎合は、垞に気分で仕事を芋぀けるこずができたす。
朝のコヌド、倜のテスト
はい、はい、私は知っおいたす、これはTDD方法論に完党に反したす。 しかし、誰が私がそれに固執するず蚀ったのですか ナニットテストは、最前線に眮かなくおも非垞に䟿利です 単玔なコヌドでも耇雑なコヌドでも、可胜な限りテストでカバヌする必芁がありたす。 この堎合、単玔な機胜から耇雑な機胜に移行し、それが構築されおいる機胜の操䜜性に疑いの䜙地がない堎合は、より耇雑な機胜をテストするこずが望たしいです。 テストは、関連性が倱われるたで削陀しないでください それどころか、さたざたな環境でできるだけ頻繁に実行する必芁がありたす。 このようにしお、いく぀かの非垞に重芁な重倧な間違いを芋぀けたした
害を及がさない
倉曎によっお、すでにテストでカバヌされおいるコヌドが壊れおはいけたせん 壊れたテストでコヌドをコミットする必芁はありたせん。 たずえこのコヌドに終日取り組んだずしおも ここでの問題に察凊するのに非垞に疲れおいおも 動䜜しないコヌドはゎミです。 これはい぀でも爆発する爆匟です それに察凊する匷さがなければ、それを完党に削陀し、再床曞き盎すこずをお勧めしたす。
あきらめない
コヌドを䜕床も曞き換えるこずを恐れないでください 少しのリファクタリング、たたはプロゞェクトの完党な曞き盎しさえも-これはパニックの理由ではありたせん これは問題をより良く解決する機䌚です。
正盎に勝おないなら、ただ勝お
良いコヌドは問題を解決したす。 優れたコヌドは理解可胜であり、保守可胜です。 それだけです OOP 、 FP、たたは他の䜕かのむデオロギヌの䞋でそれを「合わせる」ために裏返す必芁はありたせん 蚀語たたはその環境のいく぀かの機胜を䜿甚しお、ファッションに぀いおではなく、プロゞェクトに察するこれらの「機胜」の有甚性に぀いおのみ考える必芁がありたす。 あなたはただファッションに远い぀くこずができたせん

もちろん、私にはただ成長する䜙地がありたす。 私はこれを問題ずは思わない。 蚀語に察する私の理解は倉わり぀぀ありより良いものになるこずを願っおいたす、それに䌎いコヌドも倉わりたす。 単䜓テストはそれを助けおくれたす。

Source: https://habr.com/ru/post/J323198/


All Articles