Կոդի կրկնօրինակումը ծրագրային ապահովման ճարտարագիտության ամենավատ հակաօրինաչափություններից մեկն է, որն ի վերջո հանգեցնում է խելագարված և չպահպանվող համակարգերի:

Ծրագրային ապահովման ճարտարագիտության հիմնարար սկզբունքը «Մի կրկնիր ինքներդ» առաջին անգամ ստեղծվել է Էնդի Հանթի և Դեյվ Թոմասի կողմից իրենց «Պրագմատիկ ծրագրավորողը» գրքում, որտեղ նրանք նշում են հետևյալը.

«Ցանկացած գիտելիք պետք է ունենա մեկ, միանշանակ, հեղինակավոր ներկայացվածություն համակարգում»:[1]

DRY սկզբունքը դուրս է գալիս զուտ կոդերի կրկնօրինակումից և ներառում է համակարգի բոլոր ասպեկտները, ինչպիսիք են տվյալների բազայի մոդելները, կառուցման համակարգերը, փաստաթղթերը և այլն, չնայած կոդերի կրկնօրինակումը դրա հիմնարար մասն է:

Մարտին Ֆաուլերի «Refactoring» գրքում նկարագրված կոդի առաջին հոտը կոդերի կրկնօրինակումն է, և Քենթ Բեքի աջակցության հետ մեկտեղ նրանք պնդում են հետևյալը.

«Գարշահոտ շքերթի թիվ մեկը կրկնօրինակված ծածկագիր է: Եթե ​​դուք տեսնում եք նույն կոդի կառուցվածքը մեկից ավելի վայրերում, կարող եք վստահ լինել, որ ձեր ծրագիրն ավելի լավը կլինի, եթե գտնեք դրանք միավորելու միջոց»:[2]

Ռոբերտ Ք. Մարտինը կոդերի կրկնօրինակման մասին ասում է հետևյալը.

«Կրկնօրինակ կոդը բոլոր չարիքների արմատն է ծրագրային ապահովման նախագծման մեջ: Երբ համակարգը լցված է նույնական կամ գրեթե նույնական կոդի բազմաթիվ հատվածներով, դա վկայում է անփութության, անփութության և բացարձակ ոչ պրոֆեսիոնալիզմի մասին: Բոլոր ծրագրային ապահովման մշակողների մեղքի պատասխանատվությունն է արմատախիլ անել և վերացնել կրկնօրինակումը, երբ գտնեն այն»:[3]

«Անփութություն», «անզգուշություն» և «բացարձակ ոչ պրոֆեսիոնալիզմ» ուժեղ բառեր են, բայց դա ընդգծում է ծածկագիրը չկրկնելու կարևորությունը: Ծրագրային ապահովման ինժեներներից շատերը պետք է ծանոթ լինեն DRY-ի հիմնարար սկզբունքին, սակայն, այնուամենայնիվ, հազվադեպ չէ որևէ նախագծում կոդի չարդարացված կրկնօրինակում տեսնելը:

Բացի այն դեպքերից, երբ կան ակնհայտ կառուցվածքային կոդերի կրկնօրինակումներ, որոնք կարող էին հեշտությամբ հեռացվել, օրինակ՝ ծածկագիրը մեկուսացված մեթոդների կամ դասերի վերօգտագործման համար հանելով, այս գրառումը ավելի շուտ կկենտրոնանա իմաստային կոդերի կրկնօրինակման վրա՝ կոդի կրկնօրինակման տեսակը, որն ավելի դժվար է: նշեք և վերացրեք, օրինակ՝ կոդում կրկնվող վարքագիծը և օրինաչափությունները:

Կառուցվածքային ընդդեմ իմաստային կոդի կրկնօրինակում

Ես սիրում եմ տարբերակել «կառուցվածքային» և «իմաստային» կոդերի կրկնօրինակումը:

Կառուցվածքային կրկնօրինակումը ներառում է հայտարարություններ գրելը կամ պատճենելը, որոնք արդեն գրվել են այլ տեղ, կամ փոփոխականների կամ արժեքների կրկնօրինակում, որոնք ներկայացնում են նույնը:

Սեմալիստական ​​կոդի կրկնօրինակումը, սակայն, հաճախ ավելի դժվար է նկատել և ավելի դժվար է վերացնել: Նման կրկնօրինակումը կարող է լինել օրինաչափություններ, որոնք կատարում են ճիշտ նույն պահանջները կամ գործողությունները, բայց կոնֆիգուրացիաների կամ ներքին մեթոդի կանչերի աննշան տարբերություններով:

Աբստրակցիա փրկության համար

Քանի որ կառուցվածքային կոդի կրկնօրինակումը սովորաբար ակնհայտ է և հեշտ է վերացնել, իմաստային կրկնօրինակումը հաճախ հետ է մնում այն ​​վերացնելու գաղափարների կամ գիտելիքների բացակայության պատճառով: Այնուամենայնիվ, կա մեկ հիմնական հայեցակարգ, որը կլուծի բոլոր իմաստային ծածկագրերի կրկնօրինակումը (ինչպես նաև կառուցվածքային կրկնօրինակումը), այն է՝ «աբստրակցիա»:

Ռոբերտ Ք. Մարտինը հաստատում է այս պնդումը՝ փաստարկելով հետևյալը.

«Կրկնօրինակ կոդը միշտ ներկայացնում է բացակայող աբստրակցիա»:[3]

Աբստրակտը կոնկրետի հակառակն է։ Սա նշանակում է, որ մեր կոդի աբստրակցիան մեծացնելու համար մենք պետք է կրճատենք կամ հեռացնենք մեր կոդի կոնկրետ ագույցները։

Օրինակ՝ կրկնօրինակված կոդերի օրինաչափությունների վերացում՝ թույլ զուգակցված աբստրակցիաներով

Նախքան սկսելը, այս գրառման օրինակի կոդի մշակման յուրաքանչյուր քայլ հատկորոշվում է որպես տարբերակի համարով կոդերի պահոցում, այնպես որ հեշտ է նայել կոնկրետ օրինակի իրականացմանը և ուսումնասիրել այն: Կոդը գրված է C# / .NET Core-ով և գտնվում է AbstractCodingպահեստում:

Ստորև բերված օրինակը ցույց է տալիս, թե ինչպես կարող են կրկնօրինակված կոդերի օրինաչափությունները վերացվել տարբեր մակարդակների աբստրակցիաների միջոցով: Օրինակն այնքան պարզ է, որ վերջնական լուծումը կարող է թվալ չափազանց մշակված, բայց դրա հիմքում ընկած սկզբունքներն են, որոնք պետք է ընդունվեն:

Այժմ պատկերացրեք մի օրինակ, որտեղ մենք ցանկանում ենք տարբեր HTTP հարցումների կատարման ֆունկցիոնալությունը, ինչպիսիք են POST, PUT, PATCH և GET, և ապասերիալացնել պատասխանը, ինչպես ցանկացած տրված ընդհանուր տիպ: Տարբեր մեթոդները պետք է վերցնեն HTTP հաճախորդը և հարցման URI/ուղին որպես արգումենտ, կատարեն հարցումը և վերադարձնեն ապասերիալացված պատասխանը որպես տրամադրված ընդհանուր տիպ:

Օրինակ, զանգահարողը կարող է ունենալ HTTP հաճախորդի օրինակ, հարցման URI և «Person» դասի տեսակ, որով HTTP հարցման պատասխանը պետք է ապասերիալացվի:

v0.1.0 - կրկնօրինակված կոդ

Կոդը հասանելի է 0.1.0 թողարկման ժամանակ:

Այս ֆունկցիոնալության համար հումքային կոդի առաջին նախագիծը կարող է այսպիսի տեսք ունենալ.

Այստեղ մենք կարող ենք տեսնել, որ կոդերի օրինաչափությունները գրեթե նույնական են: Նրանք երկուսն էլ կատարում են HTTP հարցում, ապասերիալացնում են HTTP պատասխանի բովանդակությունը և նետում են HttpRequestException, եթե հարցումը կատարելիս սխալ է տեղի ունենում: Կա միայն մեկ բան, որը տարբերում է օրինաչափությունները. մասնավորապես HTTP տարբեր մեթոդների կատարումը: Ինչպե՞ս կարող ենք հեռացնել կրկնօրինակված օրինաչափությունը, երբ նախշը ներքուստ կանչում է տարբեր մեթոդներ: Մենք շուտով կանդրադառնանք դրան, բայց նախ եկեք նայենք կոդի կառուցվածքային կրկնօրինակմանը:

v0.1.1 — հեռացնել կառուցվածքային կոդի կրկնօրինակումը

Կոդը հասանելի է 0.1.1 թողարկման ժամանակ:

v0.1.0-ում կան կառուցվածքային կոդի կրկնօրինակումներ, քանի որ տարբեր մեթոդներ օգտագործում են նույն կոդը՝ HTTP պատասխանը ստանալու և ապասերիալիզացնելու համար, ինչպես նաև նույն բացառությունը նույն կոշտ կոդավորված հաղորդագրությամբ նետելու համար: Քանի որ միշտ չէ, որ պարզ և հեշտ է հեռացնել կրկնօրինակ կոդերի օրինաչափությունները և վարքագիծը, շատ ծրագրավորողներ, անկասկած, կվերացնեն կառուցվածքային կոդի կրկնօրինակումը և կհեռանան դրանով.

v0.2.0 — կրկնօրինակ կոդի օրինակի չմշակված վերացում

Կոդը հասանելի է 0.2.0 թողարկման ժամանակ:

Այժմ, v0.1.1-ի կոդը հաստատ ավելի լավ է, քան v0.1.0-ը, բայց կոդի օրինաչափությունները դեռ կրկնօրինակված են: Այս կրկնօրինակումը հեռացնելու համար մենք պետք է նայենք այն կողմին, որը բաժանում է այս օրինաչափությունները միմյանցից: Տարբեր HTTP հարցումների կատարումը կատարվում է ճիշտ նույն պայմանագրով, որում նրանք բոլորը վերադարձնում են HTTP պատասխան հաղորդագրություն: Մենք կարող ենք պարամետրավորել այս տարբեր հարցումները որպես ֆունկցիաներ և դրանք փոխանցել որպես փաստարկ այժմ մեկ մեթոդի, որը պարունակում է ամբողջ գործողությունը, որը հասանելի է վերաօգտագործման համար.

Սա հիանալի է. մենք վերացրել ենք կրկնօրինակ կոդերի օրինակը՝ փոխանցելով տարբեր HTTP հարցումները որպես ֆունկցիաներ օրինաչափություն պարունակող մեկ մեթոդի: Ցածր մակարդակի իրականացման սցենարներում, ինչպիսին է տվյալների բազայի կոնկրետ մուտքը, կարծում եմ, որ կոդը կարելի է թողնել այսպես: Այնուամենայնիվ, բարձր մակարդակի քաղաքականությունների համար, ինչպիսիք են բիզնեսի կանոնները, կոդը, անկասկած, չափազանց սերտորեն կապված է HTTP հարցումների կոնկրետ կատարման հետ, ինչը նշանակում է, որ կոդը պետք է ուղղված լինի վերացականության ավելի բարձր մակարդակի:

Ներկայիս օրինակը նույնպես շատ պարզ է՝ թողնելով գործառույթների փոխանցումը մեթոդին բավականին հեշտ հետևելու համար: Այնուամենայնիվ, ֆունկցիաները իմաստային մոդելավորված չեն այն իմաստով, որ դրանք պարզապես տեղեկատվություն են տրամադրում այն ​​մասին, թե ինչ տեսակներ են նրանք ընդունում որպես արգումենտ և ինչ տեսակ են վերադարձնում, բայց անհնար է իմանալ, թե ինչ են ներկայացնում արգումենտները և վերադարձի տեսակը: . Այս դեպքում չկա հստակ տեղեկատվություն այն գործառույթների մասին, որոնք հարցումը որպես արգումենտ են ընդունում, այն պարզապես սահմանում է, որ այն ընդունում է լարային տեսակ: Ավելի բարդ սցենարների դեպքում դա կդժվարացնի կոդին հետևելը: Այս ասելով, ես անպայման կնախընտրեի գործառույթ-մոտեցումը, որը վերացնում է կրկնօրինակ կոդի ձևը, քան կոդ-կրկնօրինակումը:

v1.0.0 — կրկնօրինակ կոդի օրինակի ինտուիտիվ վերացում

Կոդը հասանելի է 1.0.0 թողարկման ժամանակ:

Հիմա փորձենք կոդը տեղափոխել հաջորդ մակարդակ. ինչպե՞ս կարող ենք այս գործառույթները, որոնք մենք անցնում ենք որպես արգումենտ, ավելի ինտուիտիվ սահմանելու համար: Դե, քանի որ գործառույթները հետևում են ճիշտ նույն պայմանագրին, մենք կարող ենք ավելի շուտ մոդելավորել սա որպես վերացական դաս: Մենք կարող ենք այս դասը անվանել «HttpRequest» և ավելացնել վերացական մեթոդ, որը կոչվում է «Execute»:

HttpRequest դասը վերցնում է հարցումը URI-ն և HttpClient-ը որպես անվանված արգումենտ իր կոնստրուկտորին, ինչը այն դարձնում է ավելի ինտուիտիվ՝ համեմատած նախորդ օրինակում մեր ներդրած ֆունկցիաների անանուն արգումենտների հետ: Գործառույթը որպես արգումենտ վերցնելու փոխարեն, ամբողջ տրամաբանությունը պարունակող մեթոդն այժմ ընդունում է HttpRequest օրինակ և կոչում է իր «Execute» մեթոդը.

Այժմ, փոխանակ ստեղծելու գործառույթներ, որոնք փոխանցվում են մեթոդին որպես փաստարկներ, կոդը այժմ ավելի շուտ ստեղծում է դասերի օրինակներ, որոնք ընդլայնում են «HttpRequest» աբստրակտ դասը: Այս դեպքերից յուրաքանչյուրն արտացոլում է մեկ կոնկրետ HTTP հարցման մեթոդ, ինչը նշանակում է, որ մենք ունենք մեկ իրականացում POST-ի, PUT-ի, PATCH-ի և GET-ի համար: Այս HTTP հարցումների դասերից յուրաքանչյուրը անտեսում է իրենց սուպերդասի «HttpRequest» «Կատարել» մեթոդը, որտեղ գտնվում է նրանց HTTP հարցումի իրականացումը: Կոդի օրինաչափությունը պարունակող մեթոդը, սակայն, չի հետաքրքրում այս կոնկրետ իրականացումներին, այն պարզապես ընդունում է «HttpRequest» դասի ցանկացած տեսակ՝ օգտվելով օբյեկտի վրա հիմնված պոլիմորֆիզմից[4]: Եթե ​​ցանկանում եք տեսնել, թե ինչպես են կառուցված HttpRequest դասը և դրա իրականացումները, ազատ զգալ դիտեք տվյալ օրինակի կոդը 1.0.0 թողարկման մեջ:

v2.0.0 — ցածր մակարդակի իրականացումների վերացում և անջատում

Կոդը հասանելի է 2.0.0 թողարկման ժամանակ:

v1.0.0-ում կոդը ևս մեկ անգամ բարելավվել էր. այն դարձել էր ավելի ինտուիտիվ և ավելի հեշտ էր երկարաձգել ու պահպանել: Կրկնօրինակված կոդի ձևանմուշն արդեն վերացվել է օրինակ 0.2.0-ում, ուստի կոդը այժմ պարզապես ցույց է տալիս, որ կոդերի օրինաչափությունները կարող են վերացվել՝ միաժամանակ պահպանելով կոդի ինտուիտիվության մակարդակը: Հիմա, եթե մենք այս օրենսգիրքը վերաբերվենք որպես մեր համակարգի բիզնեսի կանոնները պարունակող բարձր մակարդակի քաղաքականություն, ես ավելի հեռուն կբերեի վերացականության մակարդակը:

Կոդը դեռևս հիմնված է կոնկրետ իրագործումների վրա, քանի որ HttpRequest-ի տարբեր կոնկրետ իրականացումներն ուղղակիորեն տեղադրվում են կոդի մեջ: Որպեսզի այս ծածկագիրը ամբողջովին թույլ զուգակցվի, մենք պետք է հեռացնենք մեր կոդի այս խտությունը: Մենք կներարկենք ինտերֆեյս, որը պատասխանատու է այս տարբեր HttpRequest-ների ստեղծման համար որպես գործարան: Ավելին, մենք կարող ենք թույլ տալ, որ HttpRequest-ն իրականացնի IHttpRequest ինտերֆեյս, որպեսզի մենք կարողանանք ցանկացած պահի տեղադրել HTTP հարցումների մոդելավորման այլ եղանակներ: Այնուհետև ծածկագիրը կարող է այսպիսի տեսք ունենալ.

Այժմ կոդը լիովին անջատված է ցանկացած իրականացումից: Սա նշանակում է, որ HttpRequestOperator դասն այլևս կախված չէ HTTP-ին հատուկ իրականացումներից: Սա նշանակում է, որ թեստերը նույնպես արմատապես փոխվում են. դրանք այժմ նույնպես լիովին անջատված են, և HttpRequestOperator դասի բոլոր գործառույթներն այժմ կարող են ծաղրվել, ինչը նշանակում է, որ դրանք նույնպես կախված չեն որևէ կոնկրետ HTTP իրականացումից: Կոդն այժմ դարձել է ավելի հեշտ է փորձարկել, ինչպես նաև ավելի հեշտ է երկարաձգել և պահպանել երկարաժամկետ հեռանկարում: Այս ամենը պաշտպանում է «Կախվածության շրջադարձը»[5], «Միայն պատասխանատվությունը»[6] և «Բաց-փակ»[7] սկզբունքները:

Վերջնական խոսքեր

Մենք դիտարկել ենք կառուցվածքային և իմաստային կոդերի կրկնօրինակման միջև եղած տարբերությունը և ուսումնասիրել ենք մի օրինակ, թե ինչպես կարելի է վերացնել կոդերի կրկնօրինակների օրինաչափությունները տարբեր ձևերով, ընդ որում վերացականությունը բանալին է վերացված կոդերի կրկնօրինակումը թե՛ ինտուիտիվ, թե՛ թույլ զուգակցված դարձնելու համար:

Իր «Մաքուր ճարտարապետություն» գրքում Ռոբերտ Ք. Մարտինը պնդում է, որ

«Լավ ճարտարապետը առավելագույնի է հասցնում չընդունված որոշումների թիվը»:[8]

Սրանով նա նկատի ունի այն, որ կոդը և բաղադրիչները պետք է տարանջատվեն աբստրակցիաներով, որպեսզի ցածր մակարդակի որոշումները, ինչպիսիք են տվյալների բազայի իրականացումը, հնարավորինս հետաձգվեն, քանի որ տեխնիկական լուծումների և բիզնես տիրույթի մասին գիտելիքները մեծանում են:

Ավելին, վերացականությունը կոդի կրկնօրինակումը վերացնելու, ճիշտ ժամանակին ճիշտ որոշումներ կայացնելու, ռիսկերն ու ծախսերը վերացնելու և համակարգը հնարավորինս հեշտ ու էժան դարձնելու բանալին է:

Հղումներ

[1] Hunt, A. & Thomas, D. (2020): Պրագմատիկ ծրագրավորողը. ձեր ճանապարհորդությունը դեպի վարպետություն, 20-րդ տարեդարձի հրատարակություն:

[2] Ֆաուլեր, Մ. (2019): Refactoring. Improving the Design of Existing Code,2-րդ հրատարակություն:

[3] Martin, R. C. (2009): Ռոբերտ Ս. Մարտինի Մաքուր կոդով շաբաթվա խորհուրդ #1. Պատահական դոփելգեր ռուբիում: Հասանելի է հետևյալ հասցեով՝ https://www.informit.com/articles/article.aspx?p=1313447 .

[4] Վիքիպեդիա (2019)։ Պոլիմորֆիզմ (համակարգչային գիտություն): Հասանելի է` https://en.wikipedia.org/wiki/Polymorphism_(computer_science):

[5] Վիքիպեդիա (2020)։ Կախվածության հակադարձման սկզբունքը: Հասանելի է հետևյալ հասցեով՝ https://en.wikipedia.org/wiki/Dependency_inversion_principle:

[6] Վիքիպեդիա (2020)։ Միասնական պատասխանատվության սկզբունք: Հասանելի է հետևյալ հասցեով՝ https://en.wikipedia.org/wiki/Single_responsibility_principle:

[7] Վիքիպեդիա (2020)։ Բաց-փակ սկզբունք: Հասանելի է հետևյալ հասցեով՝ https://en.wikipedia.org/wiki/Open-closed_principle:

[8] Martin, R. C. (2018): Մաքուր ճարտարապետություն. ծրագրային ապահովման կառուցվածքի և դիզայնի վարպետի ուղեցույց: