미래 지향적인 웹 앱 구축: The Codest의 전문가 팀이 제공하는 인사이트
The Codest가 최첨단 기술로 확장 가능한 대화형 웹 애플리케이션을 제작하고 모든 플랫폼에서 원활한 사용자 경험을 제공하는 데 탁월한 성능을 발휘하는 방법을 알아보세요. Adobe의 전문성이 어떻게 디지털 혁신과 비즈니스를 촉진하는지 알아보세요...
대부분의 개발자는 밥 아저씨의 SOLID 원칙 중 하나인 개방형-폐쇄형 원칙에 대해 들어보셨을 것입니다. 합리적으로 들리지만 '라이브' 코드에서 처음 사용하기 전까지는 여전히 약간 모호할 수 있습니다. 원칙의 전체 상태는 소프트웨어 엔티티(클래스, 모듈, 함수 등)는 확장을 위해서는 개방적이어야 하지만 수정을 위해서는 폐쇄적이어야 한다는 것입니다.
저희는 개방형-폐쇄형 원칙이 실제로 어떤 것인지 보여주는 개발 문제를 발견했습니다. 웹 애플리케이션 중 하나에 두 개의 섹션이 있는 양식이 있었습니다:
사용자는 원하는 만큼 필터를 추가할 수 있지만 몇 가지 규칙이 있습니다. 필터 사용 가능 여부는 선택한 채널에 따라 다릅니다.
수요 채널: 광고교환, 헤더입찰, 예약, 기타 동적 필터(차원): 웹사이트, 광고단위, 지역, 크리에이티브크기, 장치
이 글은 대부분 코드 리팩터링에 관한 것이므로 아래에 많은 코드 조각이 있을 것입니다. 최대한 줄이려고 노력했지만 다음과 같은 내용을 보여드리기 위해 어느 정도의 코드가 필요합니다. 코드 리팩토링. 주요 아이디어를 얻기 위해 코드의 작은 부분까지 모두 이해할 필요는 없습니다.
리서치폼스테이트업데이터 클래스 {
update () {
(...)
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) => {
// 웹사이트 필터 비활성화
});
}
}
보시다시피 웹사이트 필터는 헤더에 사용할 수 없는 것으로 되어 있습니다.입찰, 예약 및 기타 채널에서 사용할 수 있으므로 광고에만 사용할 수 있습니다.교환 채널.
코드에 대해 마지막으로 말할 수 있는 것은 영구적이거나 정적이라는 것입니다. 따라서 더 크고 복잡한 클래스를 만들어 달라는 고객의 요청이 많아지고 있습니다.
다른 채널 추가 - EBDA (EBDA를 선택하는 동안에는 웹사이트 필터를 사용할 수 없어야 합니다):
첫 번째 구현에서는 메서드와 상수 이름에 웹사이트를 지정했습니다. 예를 들어
스포일러 경고 -> 컴포넌트가 변경을 위해 열려 있으면 앞으로 많은 이름 변경이 있을 것입니다. 다음 단계에서는 이에 대해서는 신경 쓰지 않겠습니다.
'제품'에 대한 다른 필터 추가 (제품 필터 가용성 체계는 웹사이트와 동일)
더 크게 가서 채널-> '소스' 위에 스위처를 추가해 보겠습니다. 지금까지 사용하던 모든 수요 채널은 애드 매니저 소스에 있습니다. 새 소스인 SSP에는 수요 채널이 없으며 사용 가능한 필터는 웹사이트뿐입니다.
규칙:
구현:
'SSP'를 선택한 경우:
광고 관리자가 확인했을 때
'플랫폼'에 대한 다른 필터 추가
규칙:
난이도:
코드 복잡성을 보여드리기 위해 다음 스니펫을 보여드리겠습니다. 숨겨도 상관없습니다.
리서치폼스테이트업데이터 클래스 {
update () {
(...)
this._triggerCallbacks();
}
_triggerCallbacks () {
// 소스에 따라 콜백 선택
}
_adManagerSourceCallbacks () {
(...)
this._enableDemandChannels(ResearchFormStateUpdater.AD_MANAGER_DEMAND_CHANNELS);
this._updateDefaultStateOfDynamicFilters();
this._updateAdManagerDynamicFilters();
}
_sspSourceCallbacks () {
(...)
this._removeDemandChannelsActiveClassAndDisable(리서치폼스테이트업데이터.AD_MANAGER_DEMAND_CHANNELS);
이._업데이트디폴트스테이트오브다이나믹필터();
}
_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 클래스 {
// 현재 구현 복잡성을 보여주기 위해 이 두 메서드 본문을 단순화하지 않았습니다.
_setDefaultDynamicFiltersToggleEvent () {
$(this._getBody()).on('dynamicFilter:enableSspFilters', (event, shouldEnableSspOptions) => {
this._setDefaultFiltersOptionDisabledState(shouldEnableSspOptions);
const selectedFilterDimension = this._getFiltersDimension().find('option:selected').val();
if (selectedFilterDimension === '웹사이트') { {.
this._toggleChosenFilterDisabledState(false);
} else if (selectedFilterDimension === 'platform') { {.
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) {
this._toggleChosenFilterDisabledState(shouldDisableWebsitesAndProducts);
}
this._setMethodSelectWebsiteAndProductOptionDisabledState(shouldDisableWebsitesAndProducts);
});
}
_toggleNonSspFilters (dimensionSelect, shouldDisable) {
$.each(ResearchDynamicFilter.NON_SSP_FILTERS_OPTIONS, (_, option) => { {
// 'shouldDisable'에 따라 필터 상태 토글
});
}
}
ResearchDynamicFilter.NON_SSP_FILTERS_OPTIONS = ['ad_unit', 'creative_size', 'geo', 'device', 'product'];
우리는 여전히 일부 '토글' 메커니즘을 수정했습니다. 4 개의 레버를 전환하고 예상 상태에 도달하는 것은 정말 어렵고 이제 동적 필터는 어떤 차원이 ssp 소스에 적합하지 않은지 알아야합니다.
리서치폼스테이트업데이터가 있는데, 이 기능이 담당하면 안 되는 이유는 무엇인가요?
바로 그 순간이 우리가 이러한 클래스를 리팩터링하기로 결정한 바로 그 순간입니다. 분석 중인 채널과 필터는 문제의 일부에 불과합니다. 여기에는 여러 양식 섹션이 있으며 모두 동일한 문제를 가지고 있습니다. 리팩터링은 새로운 채널이나 차원을 추가하기 위해 해당 클래스의 내부 메서드를 변경할 필요성을 무력화시켜야 합니다.
다음 스니펫에서는 주요 클래스를 프로덕션 코드에 거의 그대로 두어 이해하기 쉽도록 만들었습니다.
리서치폼스테이트업데이터 클래스 {
update () {
(...)
this._updateDynamicFilters();
}
_updateDynamicFilters () { {
this._toggleAllDynamicFiltersState(this._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);
}
disabledFilters를 반환합니다;
}
_toggleAllDynamicFiltersState (disabledFilters) {
$('.dynamic-filter').each((_, filter) => {
this._toggleDynamicFilterState(filter, disabledFilters);
});
}
_toggleDynamicFilterState (dynamicFilter, disabledFilters) {
$(dynamicFilter).trigger('dynamicFilter:toggleDynamicFilters', disabledFilters);
}
}
리서치폼스테이트업데이터.NO_SSP_필터 = ['광고_단위', '크리에이티브_사이즈', '지역', '기기', '제품'] = ['광고_단위', '크리에이티브_사이즈', '지역', '제품'];
ResearchFormStateUpdater.ONLY_SSP_FILTERS = ['플랫폼'];
리서치폼스테이트업데이터.ONLY_ADX_필터 = ['웹사이트', '제품'];
ResearchDynamicFilter 클래스 {
_setDynamicFiltersToggleEvent () {
$(this._getBody()).on('dynamicFilter:toggleDynamicFilters', (event, disabledFilters) => {
this._disableFilters(disabledFilters.split(','));
this._enableFilters(disabledFilters.split(',')));
});
}
_disableFilters (filtersToDisable) {
// disable filtersToDisable
}
_enableFilters (filtersToDisable) {
const filtersToEnable = $(ResearchDynamicFilter.ALL_FILTERS).not(filtersToDisable).get();
// filtersToEnable 활성화
}
}
ResearchDynamicFilter.ALL_FILTERS = ['웹사이트', '광고_단위', '크리에이티브_크기', '지역', '기기', '제품', '플랫폼'];
이제 'ResearchDynamicFilter'가 알아야 하는 것은 모든 필터의 목록뿐입니다. 나머지 로직과 제어는 상위 메서드와 상수를 통해 이루어집니다.
이제 'Yield_partner'에 대한 필터를 추가하여 새로운 구조를 시험해 보겠습니다:
ResearchFormStateUpdater 클래스 {
_dynamicFiltersDimensionsToBeDisabled () {
(...)
if (this.areDemandChannelsExceptEbdaSelected) { {
disabledFilters = disabledFilters.concat(ResearchFormStateUpdater.ONLY_EBDA_FILTERS);
}
disabledFilters를 반환합니다;
}
}
ResearchFormStateUpdater.NO_SSP_FILTERS = [(...), 'yield_partner'];
ResearchFormStateUpdater.ONLY_EBDA_FILTERS = [(...), 'yield_partner'];
ResearchDynamicFilter.ALL_FILTERS = [(...), 'yield_partner'];
보시다시피, 상수와 몇 가지 추가 조건에 몇 가지 값을 추가하는 것이 전부입니다.
'개방형-폐쇄형 원칙' 덕분에 우리는 더 높은 수준의 추상화에서 몇 가지 값과 조건만 추가하는 것으로 비즈니스 로직의 형태를 변경할 수 있습니다. 컴포넌트 내부로 들어가서 아무것도 변경할 필요가 없습니다. 이 리팩터링은 전체 폼에 영향을 미쳤고 더 많은 섹션이 생겼으며 이제 모두 개방-폐쇄 원칙을 따릅니다.
코드의 양을 줄이지 않았고, 오히려 (이전과 이후를 비교했을 때) 코드가 더 늘어났습니다:
상수 컬렉션 -> 이제 수십 개의 스위처 없이도 프로세스를 제어할 수 있는 콘솔인 공용 인터페이스가 있습니다.
또한 읽어보세요: