CodeGym /Java блог /Случаен /Правила за кодиране: от създаване на система до работа с ...
John Squirrels
Ниво
San Francisco

Правила за кодиране: от създаване на система до работа с обекти

Публикувано в групата
Добър ден на всички! Днес бихме искали да поговорим с вас за писането на добър code. Разбира се, не всеки иска веднага да дъвче книги като Clean Code, тъй като те съдържат огромно количество информация, но в началото не е много ясно. И докато приключите с четенето, може да убиете цялото си желание да codeирате. Имайки предвид всичко това, днес искам да ви предоставя малко ръководство (малък набор от препоръки) за писане на по-добър code. В тази статия нека разгледаме основните правила и концепции, свързани със създаването на система и работата с интерфейси, класове и обекти. Четенето на тази статия няма да отнеме много време и, надявам се, няма да ви отегчи. Ще си проправя път отгоре надолу, т.е. от общата структура на приложението до по-тесните му детайли. Правила за codeиране: от създаване на система до работа с обекти - 1

системи

Следните обикновено са желани характеристики на една система:
  • Минимална сложност. Прекалено сложните проекти трябва да се избягват. Най-важното е простотата и яснотата (по-просто = по-добре).
  • Лесна поддръжка. Когато създавате приложение, трябва да запомните, че то ще трябва да се поддържа (дори ако вие лично няма да носите отговорност за поддържането му). Това означава, че codeът трябва да е ясен и очевиден.
  • Разхлабено съединение. Това означава, че свеждаме до минимум броя на зависимостите между различните части на програмата (като максимално спазваме принципите на ООП).
  • Повторна употреба. Ние проектираме нашата система с възможност за повторно използване на компоненти в други applications.
  • Преносимост. Трябва да е лесно да се адаптира система към друга среда.
  • Униформен стил. Ние проектираме нашата система, използвайки единен стил в различните й компоненти.
  • Разширяемост (скалируемост). Можем да подобрим системата, без да нарушаваме основната й структура (добавянето or промяната на компонент не трябва да засяга всички останали).
Практически е невъзможно да се създаде приложение, което не изисква модификации or нова функционалност. Постоянно ще трябва да добавяме нови части, за да помогнем на нашето дете да бъде в крак с времето. Това е мястото, където мащабируемостта влиза в игра. Мащабируемостта по същество е разширяване на приложението, добавяне на нова функционалност и работа с повече ресурси (or, с други думи, с по-голямо натоварване). С други думи, за да улесним добавянето на нова логика, ние се придържаме към някои правила, като например намаляване на свързването на системата чрез увеличаване на модулността.Правила за програмиране: от създаване на система до работа с обекти - 2

Източник на изображението

Етапи на проектиране на система

  1. Софтуерна система. Проектирайте приложението като цяло.
  2. Разделяне на подсистеми/пакети. Дефинирайте логически обособени части и дефинирайте правилата за взаимодействие между тях.
  3. Разделяне на подсистемите на класове. Разделете частите на системата на специфични класове и интерфейси и дефинирайте взаимодействието между тях.
  4. Разделяне на класовете на методи. Създайте пълна дефиниция на необходимите методи за клас, въз основа на възложената му отговорност.
  5. Дизайн на метода. Създайте подробна дефиниция на функционалността на отделните методи.
Обикновено обикновените разработчици се справят с този дизайн, докато архитектът на приложението се занимава с точките, описани по-горе.

Общи принципи и концепции за проектиране на системи

Мързелива инициализация. В този програмен идиом приложението не губи време за създаване на обект, докато не бъде използвано действително. Това ускорява процеса на инициализация и намалява натоварването на събирача на отпадъци. Въпреки това не трябва да отивате твърде далеч, защото това може да наруши принципа на модулността. Може би си струва да преместите всички екземпляри на конструкция в няHowва конкретна част, например основния метод or във фабричен клас. Една характеристика на добрия code е липсата на повтарящ се шаблонен code. По правило такъв code се поставя в отделен клас, за да може да бъде извикан при нужда.

AOP

Бих искал да отбележа и аспектно-ориентираното програмиране. Тази парадигма на програмиране е изцяло свързана с въвеждането на прозрачна логика. Тоест, повтарящият се code се поставя в класове (аспекти) и се извиква, когато са изпълнени определени условия. Например, когато извиквате метод с конкретно име or осъществявате достъп до променлива от определен тип. Понякога аспектите могат да бъдат объркващи, тъй като не е ясно веднага откъде се извиква codeът, но това все още е много полезна функционалност. Особено при кеширане or логване. Ние добавяме тази функционалност, без да добавяме допълнителна логика към обикновените класове. Четирите правила на Кент Бек за проста архитектура:
  1. Изразителност - Намерението на класа трябва да бъде ясно изразено. Това се постига чрез правилно наименуване, малък размер и спазване на принципа на единичната отговорност (който ще разгледаме по-подробно по-долу).
  2. Минимален брой класове и методи — В желанието си да направите класовете колкото е възможно по-малки и тясно фокусирани, можете да отидете твърде далеч (което води до анти-шаблона за операция с пушка). Този принцип изисква системата да бъде компактна и да не се прекалява, създавайки отделен клас за всяко възможно действие.
  3. Без дублиране — Дублираният code, който създава объркване и е индикация за неоптимален дизайн на системата, се извлича и премества на отделно място.
  4. Изпълнява всички тестове — Система, която преминава всички тестове, е управляема. Всяка промяна може да доведе до провал на теста, разкривайки ни, че нашата промяна във вътрешната логика на метод също е променила поведението на системата по неочаквани начини.

ТВЪРД

Когато проектирате система, добре известните принципи на SOLID си заслужава да бъдат разгледани:

S (единична отговорност), O (отворено-затворено), L (заместване на Лисков), I (сегрегация на интерфейс), D (инversion на зависимост).

Няма да се спираме на всеки отделен принцип. Това би било малко извън обхвата на тази статия, но можете да прочетете повече тук .

Интерфейс

Може би една от най-важните стъпки в създаването на добре проектиран клас е създаването на добре проектиран интерфейс, който представлява добра абстракция, скривайки подробностите за изпълнение на класа и едновременно с това представяйки група от методи, които са ясно съвместими един с друг. Нека разгледаме по-отблизо един от принципите на SOLID — разделяне на интерфейса: клиентите (класовете) не трябва да прилагат ненужни методи, които няма да използват. С други думи, ако говорим за създаване на интерфейс с най-малък брой методи, насочени към изпълнение на единствената задача на интерфейса (което според мен е много подобно на принципа на единична отговорност), по-добре е instead of това да създадете няколко по-малки на един раздут интерфейс. За щастие един клас може да реализира повече от един интерфейс. Не забравяйте да наименувате правилно вашите интерфейси: името трябва да отразява възложената задача възможно най-точно. И, разбира се, колкото по-кратък е, толкова по-малко объркване ще предизвика. Коментарите към documentацията обикновено се пишат на ниво интерфейс. Тези коментари предоставят подробности за това Howво трябва да прави всеки метод, Howви аргументи приема и Howво ще върне.

Клас

Правила за програмиране: от създаване на система до работа с обекти - 3

Източник на изображението

Нека да разгледаме How са подредени класовете вътрешно. Или по-скоро някои гледни точки и правила, които трябва да се следват при писане на класове. Като правило класът трябва да започва със списък от променливи в определен ред:
  1. публични статични константи;
  2. частни статични константи;
  3. частни променливи на екземпляр.
Следват различните конструктори, подредени от тези с най-малко аргументи до тези с най-много. Те са последвани от методи от най-публичните до най-частните. Най-общо казано, частните методи, които скриват изпълнението на някои функции, които искаме да ограничим, са най-отдолу.

Размер на класа

Сега бих искал да говоря за размера на класовете. Нека си припомним един от принципите на SOLID — принципът на единната отговорност. Той гласи, че всеки обект има само една цел (отговорност) и логиката на всичките му методи има за цел да я постигне. Това ни казва да избягваме големи, раздути класове (които всъщност са анти-модел на Божия обект) и ако имаме много методи с всяHowви различни логики, натъпкани в един клас, трябва да помислим дали да го разделим на няколко логически части (класове). Това от своя страна ще увеличи четливостта на codeа, тъй като няма да отнеме много време, за да разберем целта на всеки метод, ако знаем приблизителната цел на всеки даден клас. Също така следете името на класа, което трябва да отразява логиката, която съдържа. Например, ако имаме клас с 20+ думи в името си, трябва да помислим за рефакторинг. Всеки уважаващ себе си клас не трябва да има толкова много вътрешни променливи. Всъщност всеки метод работи с един or няколко от тях, причинявайки много сплотеност в класа (което е точно Howто трябва да бъде, тъй като класът трябва да бъде единно цяло). В резултат на това увеличаването на сплотеността на класа води до намаляване на размера на класа и, разбира се, броят на класовете се увеличава. Това е досадно за някои хора, тъй като трябва да разглеждате файловете на класа повече, за да видите How работи конкретна голяма задача. На всичкото отгоре всеки клас е малък модул, който трябва да бъде минимално свързан с другите. Тази изолация намалява броя на промените, които трябва да направим, когато добавяме допълнителна логика към клас. всеки метод работи с един or няколко от тях, причинявайки много сплотеност в класа (което е точно Howто трябва да бъде, тъй като класът трябва да бъде единно цяло). В резултат на това увеличаването на сплотеността на класа води до намаляване на размера на класа и, разбира се, броят на класовете се увеличава. Това е досадно за някои хора, тъй като трябва да разглеждате файловете на класа повече, за да видите How работи конкретна голяма задача. На всичкото отгоре всеки клас е малък модул, който трябва да бъде минимално свързан с другите. Тази изолация намалява броя на промените, които трябва да направим, когато добавяме допълнителна логика към клас. всеки метод работи с един or няколко от тях, причинявайки много сплотеност в класа (което е точно Howто трябва да бъде, тъй като класът трябва да бъде единно цяло). В резултат на това увеличаването на сплотеността на класа води до намаляване на размера на класа и, разбира се, броят на класовете се увеличава. Това е досадно за някои хора, тъй като трябва да разглеждате файловете на класа повече, за да видите How работи конкретна голяма задача. На всичкото отгоре всеки клас е малък модул, който трябва да бъде минимално свързан с другите. Тази изолация намалява броя на промените, които трябва да направим, когато добавяме допълнителна логика към клас. сплотеността води до намаляване на размера на класа и, разбира се, броят на класовете се увеличава. Това е досадно за някои хора, тъй като трябва да разглеждате файловете на класа повече, за да видите How работи конкретна голяма задача. На всичкото отгоре всеки клас е малък модул, който трябва да бъде минимално свързан с другите. Тази изолация намалява броя на промените, които трябва да направим, когато добавяме допълнителна логика към клас. сплотеността води до намаляване на размера на класа и, разбира се, броят на класовете се увеличава. Това е досадно за някои хора, тъй като трябва да разглеждате файловете на класа повече, за да видите How работи конкретна голяма задача. На всичкото отгоре всеки клас е малък модул, който трябва да бъде минимално свързан с другите. Тази изолация намалява броя на промените, които трябва да направим, когато добавяме допълнителна логика към клас.

Обекти

Капсулиране

Тук първо ще говорим за ООП принцип: капсулиране. Скриването на имплементацията не означава създаване на метод за изолиране на променливи (необмислено ограничаване на достъпа чрез отделни методи, гетери и сетери, което не е добре, тъй като се губи целият смисъл на капсулирането). Скриването на достъпа е насочено към формиране на абстракции, тоест класът предоставя споделени конкретни методи, които използваме, за да работим с нашите данни. И не е нужно потребителят да знае How точно работим с тези данни — работи и това е достатъчно.

Закон на Деметра

Можем също да разгледаме Закона на Деметра: това е малък набор от правила, които помагат при управлението на сложността на ниво клас и метод. Да предположим, че имаме обект Car и той има метод за преместване (Object arg1, Object arg2) . Според закона на Деметра този метод е ограничен до извикване на:
  • методи на самия обект Car (с други думи обектът "този");
  • методи на обекти, създадени в рамките на метода за преместване ;
  • методи на обекти, предавани като аргументи ( arg1 , arg2 );
  • методи на вътрешни обекти на автомобила (отново „това“).
С други думи, законът на Деметра е нещо подобно на това, което родителите биха могли да кажат на дете: "можеш да говориш с приятелите си, но не и с непознати".

Структура на данни

Структурата на данните е колекция от свързани елементи. Когато разглеждаме обект като структура от данни, има набор от елементи от данни, върху които работят методите. Съществуването на тези методи се предполага имплицитно. Тоест, структура от данни е обект, чиято цел е да съхранява и работи с (обработва) съхранените данни. Основната му разлика от обикновения обект е, че обикновеният обект е колекция от методи, които работят върху елементи от данни, за които имплицитно се предполага, че съществуват. Разбираш ли? Основният аспект на един обикновен обект са методите. Вътрешните променливи улесняват правилната им работа. Но в структурата на данните методите са там, за да поддържат работата ви със съхранените елементи от данни, които са от първостепенно meaning тук. Един тип структура от данни е обект за прехвърляне на данни (DTO). Това е клас с публични променливи и без методи (or само методи за четене/запис), който се използва за прехвърляне на данни при работа с бази данни, анализиране на съобщения от сокети и т.н. Данните обикновено не се съхраняват в такива обекти за дълъг период от време. Почти веднага се преобразува в типа обект, който работи нашето приложение. Субектът от своя страна също е структура от данни, но целта му е да участва в бизнес логиката на различни нива на приложението. Целта на DTO е да транспортира данни към/от приложението. Пример за DTO: също е структура от данни, но нейната цел е да участва в бизнес логиката на различни нива на приложението. Целта на DTO е да транспортира данни към/от приложението. Пример за DTO: също е структура от данни, но нейната цел е да участва в бизнес логиката на различни нива на приложението. Целта на DTO е да транспортира данни към/от приложението. Пример за DTO:

@Setter
@Getter
@NoArgsConstructor
public class UserDto {
    private long id;
    private String firstName;
    private String lastName;
    private String email;
    private String password;
}
Всичко изглежда достатъчно ясно, но тук научаваме за съществуването на хибриди. Хибридите са обекти, които имат методи за обработка на важна логика, съхраняват вътрешни елементи и също така включват методи за достъп (get/set). Такива обекти са разхвърляни и затрудняват добавянето на нови методи. Трябва да ги избягвате, защото не е ясно за Howво служат - съхраняване на елементи or изпълнение на логика?

Принципи на създаване на променливи

Нека поразсъждаваме малко върху променливите. По-конкретно, нека помислим Howви принципи се прилагат при създаването им:
  1. В идеалния случай трябва да декларирате и инициализирате променлива точно преди да я използвате (не създавайте и забравете за нея).
  2. Когато е възможно, декларирайте променливите като final, за да предотвратите промяна на стойността им след инициализация.
  3. Не забравяйте за променливите на брояча, които обикновено използваме в няHowъв вид for цикъл. Тоест не забравяйте да ги занулите. В противен случай цялата ни логика може да се счупи.
  4. Трябва да опитате да инициализирате променливи в конструктора.
  5. Ако има избор между използването на обект с препратка or без ( new SomeObject() ), изберете без, тъй като след като обектът бъде използван, той ще бъде изтрит по време на следващия цикъл на събиране на боклука и ресурсите му няма да бъдат изразходвани.
  6. Поддържайте живота на променливата (разстоянието между създаването на променливата и последния път, когато е реферирана) възможно най-кратко.
  7. Инициализирайте променливите, използвани в цикъл, точно преди цикъла, а не в началото на метода, който съдържа цикъла.
  8. Винаги започвайте с най-ограничения обхват и разширявайте само когато е необходимо (трябва да се опитате да направите променливата възможно най-локална).
  9. Използвайте всяка променлива само за една цел.
  10. Избягвайте променливи със скрита цел, например променлива, разделена между две задачи — това означава, че нейният тип не е подходящ за решаване на една от тях.

Методи

Правила за програмиране: от създаване на система до работа с обекти - 4

от филма "Междузвездни войни: Епизод III - Отмъщението на ситите" (2005)

Нека да преминем директно към прилагането на нашата логика, т.е. към методите.
  1. Правило №1 — Компактност. В идеалния случай един метод не трябва да надвишава 20 реда. Това означава, че ако публичен метод „набъбне“ значително, трябва да помислите за разбиването на логиката и преместването й в отделни частни методи.

  2. Правило #2 — if , else , while и други оператори не трябва да имат силно вложени блокове: многото вложени значително намаляват четливостта на codeа. В идеалния случай трябва да имате не повече от два вложени {} блока.

    И също така е желателно codeът в тези блокове да бъде компактен и прост.

  3. Правило #3 - Един метод трябва да изпълнява само една операция. Тоест, ако даден метод изпълнява всяHowъв вид сложна логика, ние го разделяме на подметоди. В резултат на това самият метод ще бъде фасада, чиято цел е да извика всички останали операции в правилния ред.

    Но Howво ще стане, ако операцията изглежда твърде проста, за да се постави в отделен метод? Вярно е, че понякога може да се почувствате като да стреляте с оръдие по врабчетата, но малките методи осигуряват редица предимства:

    • По-добро разбиране на codeа;
    • Методите са склонни да стават по-сложни с напредването на развитието. Ако един метод е прост в началото, тогава ще бъде малко по-лесно да се усложни неговата функционалност;
    • Подробностите за изпълнението са скрити;
    • По-лесно повторно използване на codeа;
    • По-надежден code.

  4. Правилото за понижаване — Кодът трябва да се чете отгоре надолу: колкото по-надолу четете, толкова по-дълбоко се задълбочавате в логиката. И обратното, колкото по-високо отивате, толкова по-абстрактни са методите. Например операторите switch са доста некомпактни и нежелани, но ако не можете да избегнете използването на switch, трябва да се опитате да го преместите възможно най-ниско, до методите на най-ниско ниво.

  5. Аргументи на метода — Какво е идеалното число? В идеалния случай ниHowва :) Но наистина ли се случва това? Въпреки това трябва да се опитате да имате възможно най-малко аргументи, защото колкото по-малко са, толкова по-лесно е да използвате даден метод и толкова по-лесно е да го тествате. Когато се съмнявате, опитайте се да предвидите всички сценарии за използване на метода с голям брой входни параметри.

  6. Освен това би било добре да се отделят методи, които имат булев флаг като входен параметър, тъй като това само по себе си предполага, че методът изпълнява повече от една операция (ако е вярно, тогава направете едно нещо; ако е невярно, тогава направете друго). Както писах по-горе, това не е добре и трябва да се избягва, ако е възможно.

  7. Ако методът има голям брой входни параметри (крайността е 7, но наистина трябва да започнете да мислите след 2-3), някои от аргументите трябва да бъдат групирани в отделен обект.

  8. Ако има няколко подобни (претоварени) метода, тогава подобни параметри трябва да бъдат предадени в същия ред: това подобрява четливостта и използваемостта.

  9. Когато предавате параметри на метод, трябва да сте сигурни, че всички те се използват, иначе защо ви трябват? Изрежете всички неизползвани параметри от интерфейса и свършете с него.

  10. try/catch не изглежда много добре в природата, така че би било добра идея да го преместите в отделен междинен метод (метод за обработка на изключения):

    
    public void exceptionHandling(SomeObject obj) {
        try {  
            someMethod(obj);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    

Говорих за дублиращия се code по-горе, но нека повторя още веднъж: Ако имаме няколко метода с повтарящ се code, трябва да го преместим в отделен метод. Това ще направи Howто метода, така и класа по-компактни. Не забравяйте за правилата, които управляват имената: подробности за правилното именуване на класове, интерфейси, методи и променливи ще бъдат обсъдени в следващата част на статията. Но това е всичко, което имам за теб днес.
Коментари
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION