将来を見据えたウェブ・アプリケーションの構築:The Codestのエキスパート・チームによる洞察
The Codestが、最先端技術を駆使してスケーラブルでインタラクティブなウェブアプリケーションを作成し、あらゆるプラットフォームでシームレスなユーザー体験を提供することにどのように秀でているかをご覧ください。The Codestの専門知識がどのようにデジタルトランスフォーメーションとビジネス...
ほとんどの開発者は、ボブおじさんのSOLID原則の1つであるオープン-クローズの原則について聞いたことがあるだろう。理にかなっているように聞こえるが、「生きた」コードで最初に使うまでは、まだ少しぼやけていることがある。原則の全容は次のとおりだ:ソフトウェアの実体(クラス、モジュール、関数など)は、拡張に対してはオープンであるべきだが、変更に対してはクローズであるべきだ。
私たちは、オープン・クローズの原則の本当の意味を教えてくれた開発問題に遭遇しました。あるウェブ・アプリケーションの中に、2つのセクションを持つフォームがありました:
ユーザーは好きなだけフィルターを追加することができるが、いくつかのルールがある。
需要チャンネルAD交換、ヘッダー入札、予約、その他 動的フィルター(次元):ウェブサイト、広告ユニット、ジオ、クリエイティブサイズ、デバイス
この記事は主にコード・リファクタリングに関するものなので、以下にコード・スニペットがたくさん出てくる。なるべく少なくするように努めたつもりだが、以下のようなコードを示すにはある程度の量が必要だ。 コード・リファクタリング.主旨を理解するためにコードの細かい部分まで理解する必要はない。
クラスResearchFormStateUpdater { {}の
更新 () {
(...)
this._updateDynamicFilters();
}
_updateDynamicFilters(){を更新します。
$('.dynamic-filter').each((_、filter) => {。
$(filter).trigger('dynamicFilter:disableWebsites', this._shouldDisableWebsitesFields());
});
}
_shouldDisableWebsitesFields(){を返します。
return this._shouldDisableFields(ResearchFormStateUpdater.WEBSITE_DISABLING_DEMAND_CHANNELS);
}
_shouldDisableFields(disablingDemandChannels){」を返します。
// disablingDemandChannels のいずれかがチェックされているか?
}
}
ResearchFormStateUpdater.WEBSITE_DISABLING_DEMAND_CHANNELS = ['header_bidding', 'reservation', 'other'];
クラスResearchDynamicFilter
_setDynamicFilterDisableWebsitesEvent(){を設定します。
$(this._getBody()).on('dynamicFilter:disableWebsites', (event, shouldDisableWebsites) => {)
// ウェブサイト・フィルタを無効にする
});
}
}
ご覧のように、ウェブサイト・フィルターはHEADERでは利用できないことになっている。BIDDING、RESERVATION、OTHERの各チャンネルはADのみ視聴可能です。EXCHANGEチャンネル。
コードについて最後に言えることは、それは永久的なもの、あるいは静的なものだということだ。そのため、クライアントからの要求が増え、これらのクラスはより大きく、より複雑になっている。
別のチャンネルを追加 - EBDA (EBDAが選択されている間、ウェブサイトフィルターは利用できないはずである):
最初の実装では、メソッド名と定数名にウェブサイトを指定した。例えば
ネタバレ注意 -> コンポーネントが変更のためにオープンになっている場合、将来的に多くの名前が変更されるでしょう。次のステップでは、このことには注意を払わない。
製品'に別のフィルターを追加する (製品 フィルタの可用性スキームはウェブサイトと同じ)
もっと大きくして、チャンネル→「ソース」の上にスイッチャーを追加しよう。 今まで持っていたデマンドチャンネルはすべて広告マネージャのソースにある。新しいソース(SSP)にはデマンドチャンネルがなく、利用可能なフィルターはウェブサイトだけです。
ルール
実施する:
SSP』を選択した場合:
広告マネージャがチェックしたとき:
プラットフォーム」に別のフィルターを追加する
ルール
難易度が高い:
次のスニペットは、主にコードの複雑さを示すためのものです。自由に隠しておいてください。
クラスResearchFormStateUpdater
更新 () {
(...)
this._triggerCallbacks();
}
_triggerCallbacks(){。
// ソースに応じてコールバックを選択
}
_adManagerSourceCallbacks(){は、次のとおりです。
(...)
this._enableDemandChannels(ResearchFormStateUpdater.AD_MANAGER_DEMAND_CHANNELS);
this._updateDefaultStateOfDynamicFilters();
this._updateAdManagerDynamicFilters();
}
_sspSourceCallbacks(){次のようになります。
(...)
this._removeDemandChannelsActiveClassAndDisable(ResearchFormStateUpdater.AD_MANAGER_DEMAND_CHANNELS);
this._updateDefaultStateOfDynamicFilters();
}
_updateDefaultStateOfDynamicFilters(){。
$('.dynamic-filter').each((_、filter) => {。
$(filter).trigger('dynamicFilter:enableSspFilters', this.isSourceSsp);
});
}
_updateAdManagerDynamicFilters(){。
$('.dynamic-filter').each((_、filter) => {。
$(filter).trigger('dynamicFilter:disableWebsitesAndProducts', this._areFormStateDimensionsDisabled() && !this.isSourceSsp);
});
}
_shouldDisableFields(disablingDemandChannels){。
// disablingDemandChannelsのいずれかがチェックされた場合
}
}
ResearchFormStateUpdater.AD_MANAGER_DISABLING_DEMAND_CHANNELS = ['header_bidding', 'reservation', 'other', 'ebda'];
クラス ResearchDynamicFilter
// 現在の実装の複雑さを示すために、この2つのメソッドは単純化していない。
_setDefaultDynamicFiltersToggleEvent(){を設定します。
$(this._getBody()).on('dynamicFilter:enableSspFilters',(イベント, shouldEnableSspOptions) => {
this._setDefaultFiltersOptionDisabledState(shouldEnableSspOptions);
const selectedFilterDimension = this._getFiltersDimension().find('option:selected').val();
if (selectedFilterDimension === 'website') { 以下のようになります。
this._toggleChosenFilterDisabledState(false);
もし (selectedFilterDimension === 'platform') { if
this._toggleChosenFilterDisabledState(!shouldEnableSspOptions);
} else {
this._toggleChosenFilterDisabledState(shouldEnableSspOptions);
}
});
}
_setDynamicFilterDisableWebsitesAndProductsEvent(){次のようになります。
$(this._getBody()).on('dynamicFilter:disableWebsitesAndProducts', (event, shouldDisableWebsitesAndProducts) => {)
const selectedFilterDimension = this._getFiltersDimension().find('option:selected').val();
if ($.inArray(selectedFilterDimension, ['website', 'product']) >= 0) { { ($.inArray(selectedFilterDimension, ['website', 'product']) >= 0)
this._toggleChosenFilterDisabledState(shouldDisableWebsitesAndProducts);
}
this._setMethodSelectWebsiteAndProductOptionDisabledState(shouldDisableWebsitesAndProducts);
});
}
を設定します。
$.each(ResearchDynamicFilter.NON_SSP_FILTERS_OPTIONS,(_、option) => { // 'SSP'に応じてフィルタの状態をトグルする。
// 'shouldDisable'に応じてフィルタの状態をトグルする。
});
}
}
ResearchDynamicFilter.NON_SSP_FILTERS_OPTIONS = ['ad_unit', 'creative_size', 'geo', 'device', 'product'];
今でもいくつか使っている。 トグル のメカニズムです。4つのレバーを切り替えて期待通りの状態にするのは本当に難しいし、今やDynamicFilterは、どの寸法がsspソース用でないかを知る必要がある。
私たちにはResearchFormStateUpdaterがある。
私たちがこれらのクラスのリファクタリングを決めたのは、まさにその瞬間だった。分析されるチャンネルとフィルターは問題のほんの一部に過ぎない。ここには複数のフォームセクションがあり、そのすべてに同じ問題があります。私たちのリファクタリングによって、これらのクラスのメソッド内部を変更する必要性がなくなります。
次のスニペットでは、メイン・クラスがどれだけわかりやすくなったかをお見せするために、本番のコードとほとんど同じにしておきました。
クラスResearchFormStateUpdater { {}の
更新 () {
(...)
this._updateDynamicFilters();
}
_updateDynamicFilters(){。
this._toggleAllDynamicFiltersState(this._dynamicFiltersDimensionsToBeDisabled());
}
_dynamicFiltersDimensionsToBeDisabled () { { { { _dynamicFiltersDimensionsToBeDisabled()
if (this.isSourceSsp) { return ResearchFormStateUpdater.NO_SSP_FILTERS; }.
var disabledFilters = ResearchFormStateUpdater.ONLY_SSP_FILTERS;
if (this.areDemandChannelsExceptAdxSelected) { 以下のようになります。
disabledFilters = disabledFilters.concat(ResearchFormStateUpdater.ONLY_ADX_FILTERS);
}
return disabledFilters;
}
_toggleAllDynamicFiltersState(disabledFilters){。
$('.dynamic-filter').each((_、filter) => {。
this._toggleDynamicFilterState(filter, disabledFilters);
});
}
_toggleDynamicFilterState (dynamicFilter, disabledFilters) { $(dynamicFilter, disabledFilters)
$(dynamicFilter).trigger('dynamicFilter:toggleDynamicFilters', disabledFilters);
}
}
ResearchFormStateUpdater.NO_SSP_FILTERS = ['ad_unit', 'creative_size', 'geo', 'device', 'product'];
ResearchFormStateUpdater.ONLY_SSP_FILTERS = ['platform'];
ResearchFormStateUpdater.ONLY_ADX_FILTERS = ['website', 'product'];
クラスResearchDynamicFilter
_setDynamicFiltersToggleEvent(){を設定します。
$(this._getBody()).on('dynamicFilter:toggleDynamicFilters',(イベント、disabledFilters) => {)
this._disableFilters(disabledFilters.split(','));
this._enableFilters(disabledFilters.split(',')));
});
}
_disableFilters (filtersToDisable) { // filtersToDisableを無効にする。
// filtersToDisableを無効にする
}
_enableFilters (filtersToDisable) { // filtersToDisable を無効にする。
const filtersToEnable = $(ResearchDynamicFilter.ALL_FILTERS).not(filtersToDisable).get();
// filtersToEnable を有効にします。
}
}
ResearchDynamicFilter.ALL_FILTERS = ['website', 'ad_unit', 'creative_size', 'geo', 'device', 'product', 'platform'];
これで、'ResearchDynamicFilter'が知る必要があるのは、すべてのフィルターのリストだけになった。残りのロジックと制御は、上位のメソッドと定数から来る。
それでは、「Yield_partner」のフィルターを追加して、新しい構造を試してみましょう:
クラスResearchFormStateUpdater
_dinamicFiltersDimensionsToBeDisabled(){」を参照してください。
(...)
if (this.areDemandChannelsExceptEbdaSelected) { disabledFilters = disabledFilters.concat(ResearchFormStateStateUpater) { (...)
disabledFilters = disabledFilters.concat(ResearchFormStateUpdater.ONLY_EBDA_FILTERS);
}
return disabledFilters;
}
}
ResearchFormStateUpdater.NO_SSP_FILTERS = [(...), 'yield_partner'];
ResearchFormStateUpdater.ONLY_EBDA_FILTERS = [(...), 'yield_partner'];
ResearchDynamicFilter.ALL_FILTERS = [(...), 'yield_partner'];
おわかりのように、定数にいくつかの値を追加し、いくつかの条件を追加するだけである。
オープン・クローズの原則」のおかげで、フォームのビジネス・ロジックを、より抽象度の高い値や条件を追加するだけで変更することができる。コンポーネントの中に入って何かを変更する必要はない。このリファクタリングはフォーム全体に影響し、さらに多くのセクションができましたが、現在ではすべてオープンクローズの原則に従っています。
私たちはコード量を減らしてはいない。むしろ増やしている(before/after):
定数のコレクションがすべてなのだ。それは今や私たちのパブリック・インターフェースであり、何十ものスイッチャーなしでプロセスをコントロールするためのコンソールなのだ。
あわせて読みたい: