Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Секрет производства: программный продукт, за который не будет стыдно

CUSTIS
October 15, 2015

Секрет производства: программный продукт, за который не будет стыдно

Открытый семинар для студентов в компании CUSTIS (15 октября 2015 года).
Лектор: Виктор Крапивин, архитектор.

CUSTIS

October 15, 2015
Tweet

More Decks by CUSTIS

Other Decks in Programming

Transcript

  1. О себе  Программирую уже 19 лет  12 лет

    в разработке на Java (Java SE, Java EE)  10 лет в «большой» разработке  В компании с 2012 года  Технический лидер (2012)  Архитектор (2014) 2/79
  2. О чем семинар? О том, что может сделать программный продукт

     «Понятным» и простым для изучения  Удобным в разработке и сопровождении  Гибким при изменениях 3/79
  3. План семинара  Декомпозиция и принципы ООП  Написание «понятного»

    кода  Перерыв  Программный продукт, удобный для разработчика  Разработка «велосипедов» 5/79
  4. Программа в парадигме ООП  Взаимодействующие объекты (Run-time)  Типы

    объектов – классы (Java, C++, C#)  Объекты имеют поведение и состояние  Поведение → методы класса  Состояние → свойства класса 7/79
  5. Элементы парадигмы ООП  Абстракция  Инкапсуляция  Наследование 

    Полиморфизм  Пятый элемент? 8/79 Здравый смысл!
  6. Декомпозиция Способ борьбы со сложностью  Разделение на части 

    Выделение связей (зависимостей) между частями  Присвоение «обязанностей» выделенным частям 10/79 «Разделяй и властвуй»
  7. Еще раз, декомпозиция – это... Способ борьбы со сложностью 

    Разделение на части  Выделение связей (зависимостей) между частями  Присвоение «обязанностей» выделенным частям 12/79
  8. Много зависимостей – плохо! Class1 имеет слишком много зависимостей 

    Усложняется повторное использование  Слишком много факторов влияет на работоспособность данного класса 13/79
  9. Много зависимостей – плохо! От класса Class4 слишком много зависит

     Изменение класса может привести к сбою во многих других классах 14/79
  10. Оценка качества декомпозиции  SOLID – набор принципов для оценки

    решения в стиле ООП  GRASP – набор правил для выделения обязанностей классов  Не дают четкого ответа или алгоритма решения, задают «направление» решения (что лучше, а что – хуже) 15/79
  11. SOLID  SRP (Single Responsibility Principle) Не должно быть больше

    одной причины для изменения класса  OCP (Open/Closed Principle) Программные элементы (модули, пакеты, классы, методы) должны быть закрыты для изменения и открыты для расширения  LSP (Liskov’s Substitution Principle) Функции, которые используют базовый тип, должны иметь возможность использовать подтипы базового типа, не зная об этом  ISP (Interface Segregation Principle) Несколько специфичных интерфейсов лучше, чем один универсальный  DIP (Dependency Inversion Principle) Абстракции не зависят от деталей, детали зависят от абстракций Подробности – в семинаре Ивана Мальцева (2013 год) 16/79
  12. SOLID. В чем недостатки класса? class ExcelReportsService extends AbstractExcelReportsService {

    public Future<ReportDocument> startReportA(Map params) { … } public Future<ReportDocument> startReportB(Map params) { … } public Future<ReportDocument> startReportC(Map params) { … } public Future<ReportDocument> startReportD(Map params) { … } @Override public File toExcelFile(ReportDocument document) { if (document instanceof HugeDocument) { return ZipFile.create(super.toExcelFile(document)); } return super.toExcelFile(document); } } 17/79
  13. SOLID. В чем недостатки класса? class ExcelReportsService extends AbstractExcelReportsService {

    public Future<ReportDocument> startReportA(Map params) { … } public Future<ReportDocument> startReportB(Map params) { … } public Future<ReportDocument> startReportC(Map params) { … } public Future<ReportDocument> startReportD(Map params) { … } @Override public File toExcelFile(ReportDocument document) { if (document instanceof HugeDocument) { return ZipFile.create(super.toExcelFile(document)); } return super.toExcelFile(document); } } 17/79
  14. SOLID. В чем недостатки класса? class ExcelReportsService extends AbstractExcelReportsService {

    public Future<ReportDocument> startReportA(Map params) { … } public Future<ReportDocument> startReportB(Map params) { … } public Future<ReportDocument> startReportC(Map params) { … } public Future<ReportDocument> startReportD(Map params) { … } @Override public File toExcelFile(ReportDocument document) { if (document instanceof HugeDocument) { return ZipFile.create(super.toExcelFile(document)); } return super.toExcelFile(document); } } 17/79
  15. SOLID. В чем недостатки класса? class ExcelReportsService extends AbstractExcelReportsService {

    public Future<ReportDocument> startReportA(Map params) { … } public Future<ReportDocument> startReportB(Map params) { … } public Future<ReportDocument> startReportC(Map params) { … } public Future<ReportDocument> startReportD(Map params) { … } @Override public File toExcelFile(ReportDocument document) { if (document instanceof HugeDocument) { return ZipFile.create(super.toExcelFile(document)); } return super.toExcelFile(document); } } 17/79
  16. GRASP General Responsibility Assignment Software Patterns  Low Coupling –

    слабое сопряжение между модулями (пакетами, классами)  High Cohesion – высокая функциональная связность между методами внутри класса, классами внутри пакета или пакетами внутри подсистем  Information Expert – обязанность назначается классу, владеющему наибольшей информацией для выполнения данной обязанности  И другие (всего 9 штук) 18/79
  17.  Почему данный класс не удовлетворяет правилу High Cohesion? class

    AdminActions { ImportResult importMicexDeals(Date onDate) {…}; ImportResult importNsdDeals(Date onDate) {…}; ImportResult importSpcexDeals(Date onDate) {…}; boolean isLastWorkingDayOfWeek(Date date) {…}; void closeMonth(Date onDate) {…}; void closeDay(Date onDate) {…}; } High Cohesion в действии 19/79
  18. High Cohesion в действии  Почему данный класс не удовлетворяет

    правилу High Cohesion? class AdminActions { ImportResult importMicexDeals(Date onDate) {…}; ImportResult importNsdDeals(Date onDate) {…}; ImportResult importSpcexDeals(Date onDate) {…}; boolean isLastWorkingDayOfWeek(Date date) {…}; void closeMonth(Date onDate) {…}; void closeDay(Date onDate) {…}; } 19/79
  19. High Cohesion в действии  Почему данный класс не удовлетворяет

    правилу High Cohesion? class AdminActions { ImportResult importMicexDeals(Date onDate) {…}; ImportResult importNsdDeals(Date onDate) {…}; ImportResult importSpcexDeals(Date onDate) {…}; boolean isLastWorkingDayOfWeek(Date date) {…}; void closeMonth(Date onDate) {…}; void closeDay(Date onDate) {…}; } 19/79
  20. High Cohesion в действии  Лучший вариант… interface DealsImporter {

    ImportResult importMicexDeals(Date onDate); ImportResult importNsdDeals(Date onDate); ImportResult importSpcexDeals(Date onDate); } interface BusinessCalendar { boolean isLastWorkingDayOfWeek(Date date); ... } interface AccountingPeriodsCloser { void closeMonth(Date onDate); void closeDay(Date onDate); } 20/79
  21. High Cohesion в действии  Лучший вариант… interface DealsImporter {

    ImportResult importMicexDeals(Date onDate); ImportResult importNsdDeals(Date onDate); ImportResult importSpcexDeals(Date onDate); } interface BusinessCalendar { boolean isLastWorkingDayOfWeek(Date date); ... } interface AccountingPeriodsCloser { void closeMonth(Date onDate); void closeDay(Date onDate); } 20/79
  22. High Cohesion в действии  Лучший вариант… interface DealsImporter {

    ImportResult importMicexDeals(Date onDate); ImportResult importNsdDeals(Date onDate); ImportResult importSpcexDeals(Date onDate); } interface BusinessCalendar { boolean isLastWorkingDayOfWeek(Date date); ... } interface AccountingPeriodsCloser { void closeMonth(Date onDate); void closeDay(Date onDate); } 20/79
  23. Слои  Задают уровни абстракции  Нижние слои не должны

    зависеть от верхних  Много вариантов «расслоения»  «Расслоение» не всегда необходимо 21/79
  24. Вариантов «расслоения» – много Упрощенное «расслоение»  UI / Presentation

    – пользовательский интерфейс (MVC)  Application – логика приложения и бизнес-логика  DataAccess – доступ к информации в БД (DAO) 22/79 UI / Presentation Application DataAccess Database
  25. Чем опасны протечки между слоями?  Появляется дефрагментация логики 

    Теряется ценность декомпозиции  Теряется контроль из «единой точки» управления  Сложнее понимать, сопровождать и изменять 24/79
  26. Чем опасны протечки между слоями?  Появляется дефрагментация логики 

    Теряется ценность декомпозиции  Теряется контроль из «единой точки» управления  Сложнее понимать, сопровождать и изменять 24/79 Держать логически связные элементы «ближе» друг к другу – хорошая идея
  27. Протечки между слоями – пессимистический сценарий 25/79 UI / Presentation

    Application DataAccess Часть бизнес- правила «П» Часть бизнес- правила «П» Часть бизнес- правила «П»
  28. Протечки между слоями – пессимистический сценарий 25/79 UI / Presentation

    Application DataAccess Часть бизнес- правила «П» Часть бизнес- правила «П» Часть бизнес- правила «П» При изменении правила менять код необходимо в нескольких местах, поэтому про это можно просто забыть
  29. Протечки между слоями package com.mycompany.eshop.dataaccess; class SqlDbOrdersDao implements OrdersDao {

    @Override public void insert(Order order) { if (isRiskyOrder(order)) { db.execute("INSERT INTO risky_orders ...", ...); } db.execute("INSERT INTO orders ...", order.getId(), ...); } private boolean isRiskyOrder(Order order) { String condition = db.callFunction("pkg_orders.risks_check(?, ?, ?)", order.getCustomer().getRegionCode(), order.getCustomer().getProfile().getJobType(), … return "Y".equals(condition); } 26/79
  30. Протечки между слоями package com.mycompany.eshop.dataaccess; class SqlDbOrdersDao implements OrdersDao {

    @Override public void insert(Order order) { if (isRiskyOrder(order)) { db.execute("INSERT INTO risky_orders ...", ...); } db.execute("INSERT INTO orders ...", order.getId(), ...); } private boolean isRiskyOrder(Order order) { String condition = db.callFunction("pkg_orders.risks_check(?, ?, ?)", order.getCustomer().getRegionCode(), order.getCustomer().getProfile().getJobType(), … return "Y".equals(condition); } 26/79
  31. Протечки между слоями package com.mycompany.eshop.dataaccess; class SqlDbOrdersDao implements OrdersDao {

    @Override public void insert(Order order) { if (isRiskyOrder(order)) { db.execute("INSERT INTO risky_orders ...", ...); } db.execute("INSERT INTO orders ...", order.getId(), ...); } private boolean isRiskyOrder(Order order) { String condition = db.callFunction("pkg_orders.risks_check(?, ?, ?)", order.getCustomer().getRegionCode(), order.getCustomer().getProfile().getJobType(), … return "Y".equals(condition); } 26/79 Этот код должен находиться в слое Application
  32. Протечки между слоями package com.mycompany.eshop.dataaccess; class SqlDbOrdersDao implements OrdersDao {

    @Override public void insert(Order order) { if (isRiskyOrder(order)) { db.execute("INSERT INTO risky_orders ...", ...); } db.execute("INSERT INTO orders ...", order.getId(), ...); } private boolean isRiskyOrder(Order order) { String condition = db.callFunction("pkg_orders.risks_check(?, ?, ?)", order.getCustomer().getRegionCode(), order.getCustomer().getProfile().getJobType(), … return "Y".equals(condition); } 26/79 Этот код должен находиться в слое Application
  33. Протечки между слоями package com.mycompany.eshop.application; public class OrdersManager { public

    void processNewOrder(Order order) { if (riskyOrderDetector.isRisky(order)) { riskyOrdersDao.insert(order); } ordersDao.insert(order); } package com.mycompany.eshop.application.businessrules; public class RiskyOrdersDetector { ... public boolean isRisky(Order order) { return "Y".equals(pkgOrders.risksCheck(order)); } } 27/79
  34. Протечки между слоями package com.mycompany.eshop.application; public class OrdersManager { public

    void processNewOrder(Order order) { if (riskyOrderDetector.isRisky(order)) { riskyOrdersDao.insert(order); } ordersDao.insert(order); } package com.mycompany.eshop.application.businessrules; public class RiskyOrdersDetector { ... public boolean isRisky(Order order) { return "Y".equals(pkgOrders.risksCheck(order)); } } 27/79
  35. Протечки между слоями package com.mycompany.eshop.application; public class OrdersManager { public

    void processNewOrder(Order order) { if (riskyOrderDetector.isRisky(order)) { riskyOrdersDao.insert(order); } ordersDao.insert(order); } package com.mycompany.eshop.application.businessrules; public class RiskyOrdersDetector { ... public boolean isRisky(Order order) { return "Y".equals(pkgOrders.risksCheck(order)); } } 27/79
  36. Протечки между слоями package com.mycompany.eshop.presentation; public class CreateOrderFormController { private

    OrdersManager ordersManager; private Stock stock; private EmailService emailService; ... protected void onCreateOrderBtnClick(ActionEvent e) { Order order = createOrderFromFormFields(); Stock.ReservationResult reservationResult = stock.reserve(order.getItems()); if (reservationResult.isSuccessful()) { ordersManager.processNewOrder(order); emailService.sendEmail( createConfirmationMailMessage(order)); } ... } 28/79
  37. Протечки между слоями package com.mycompany.eshop.presentation; public class CreateOrderFormController { private

    OrdersManager ordersManager; private Stock stock; private EmailService emailService; ... protected void onCreateOrderBtnClick(ActionEvent e) { Order order = createOrderFromFormFields(); Stock.ReservationResult reservationResult = stock.reserve(order.getItems()); if (reservationResult.isSuccessful()) { ordersManager.processNewOrder(order); emailService.sendEmail( createConfirmationMailMessage(order)); } ... } 28/79
  38. Протечки между слоями package com.mycompany.eshop.presentation; public class CreateOrderFormController { private

    OrdersManager ordersManager; private Stock stock; private EmailService emailService; ... protected void onCreateOrderBtnClick(ActionEvent e) { Order order = createOrderFromFormFields(); Stock.ReservationResult reservationResult = stock.reserve(order.getItems()); if (reservationResult.isSuccessful()) { ordersManager.processNewOrder(order); emailService.sendEmail( createConfirmationMailMessage(order)); } ... } 28/79 Этот код должен находиться в слое Application
  39. Протечки между слоями package com.mycompany.eshop.presentation; public class CreateOrderFormController { private

    OrdersManager ordersManager; private Stock stock; private EmailService emailService; ... protected void onCreateOrderBtnClick(ActionEvent e) { Order order = createOrderFromFormFields(); Stock.ReservationResult reservationResult = stock.reserve(order.getItems()); if (reservationResult.isSuccessful()) { ordersManager.processNewOrder(order); emailService.sendEmail( createConfirmationMailMessage(order)); } ... } 28/79 Этот код должен находиться в слое Application
  40. Где мы?  Декомпозиция и принципы ООП  Написание «понятного»

    кода  Перерыв  Программный продукт, удобный для разработчика  Разработка «велосипедов» 29/79
  41. Понятный код? @RequestMapping("/client/{clientId}") public void showClientForm(String clientId, HttpServletRequest httpRequest) String

    userDN = WebAppLdapUtils.getUserDN(httpRequest); User user = usersService.getUser(userDN); Client client = clientsRepository.get(clientId); if ((permissionsService.isAllowed(userDN, "ClientsBoard", "CLIENTS", "VIEW") && user.getOffice().getRegion().equals(client.getRegion())) || permissionsService.isAllowed(userDN, "ClientsBoard", "CLIENTS", "VIEW_FOR_ANY_REGION")) { ... } 32/79
  42. Понятный код? @RequestMapping("/client/{clientId}") public void showClientForm(String clientId, HttpServletRequest httpRequest) String

    userDN = WebAppLdapUtils.getUserDN(httpRequest); User user = usersService.getUser(userDN); Client client = clientsRepository.get(clientId); if ((permissionsService.isAllowed(userDN, "ClientsBoard", "CLIENTS", "VIEW") && user.getOffice().getRegion().equals(client.getRegion())) || permissionsService.isAllowed(userDN, "ClientsBoard", "CLIENTS", "VIEW_FOR_ANY_REGION")) { ... } 32/79
  43. Понятный код? ... @RequestMapping("/client/{clientId}") public void showClientForm(String clientId, HttpServletRequest httpRequest)

    { Client client = clientsRepository.get(clientId); IUser user = detectCurrentUser(httpRequest); if ((user.hasPermission(Permissions.VIEW_CLIENT) && user.isWorkingInRegionalOffice(client.getRegion())) || user.hasPermission(Permissions.VIEW_ANY_CLIENT)) { ... } ... 33/79
  44. Понятный код? ... @RequestMapping("/client/{clientId}") public void showClientForm(String clientId, HttpServletRequest httpRequest)

    { Client client = clientsRepository.get(clientId); IUser user = detectCurrentUser(httpRequest); if ((user.hasPermission(Permissions.VIEW_CLIENT) && user.isWorkingInRegionalOffice(client.getRegion())) || user.hasPermission(Permissions.VIEW_ANY_CLIENT)) { ... } ... 33/79
  45. Комментарии в исходном коде  Комментарии могут помочь  «Бестолковые»

    комментарии раздражают и мешают  Множество комментариев создает эффект «шума» или «тумана» 34/79
  46. Комментарии в коде могут «лгать»  Комментарии могут быть непонятными

    или уже не актуальными  Только исполняемый программный код «говорит правду» 35/79
  47. «Бестолковый» комментарий /** * Хранилище клиентов * @author [email protected] *

    @date 01.01.2015 */ interface ClientsRepository { /** * @param namePart * @param operator * @return * @throws DataReadException */ Client find(String namePart) throws DataReadException; … } 36/79
  48. «Бестолковый» комментарий /** * Хранилище клиентов * @author [email protected] *

    @date 01.01.2015 */ interface ClientsRepository { /** * @param namePart * @param operator * @return * @throws DataReadException */ Client find(String namePart) throws DataReadException; … } 36/79
  49. «Бестолковый» комментарий /** * Хранилище клиентов * @author [email protected] *

    @date 01.01.2015 */ interface ClientsRepository { /** * @param namePart * @param operator * @return * @throws DataReadException */ Client find(String namePart) throws DataReadException; … } 36/79
  50. «Бестолковый» комментарий / итерируемся по массиву for (int i =

    0; i < array.length; i++) { ... } // добавляем к дате 1 день asOfDate = DateUtils.addDays(asOfDate, 1); 37/79 Капитан Очевидность
  51. «Бестолковый» комментарий / итерируемся по массиву for (int i =

    0; i < array.length; i++) { ... } // добавляем к дате 1 день asOfDate = DateUtils.addDays(asOfDate, 1); 37/79 Капитан Очевидность
  52. Полезные комментарии  Объяснение сути и причин неочевидного или спорного

    решения  Пояснение «контракта» метода или класса, если «контракт» не очевиден из его сигнатуры  Пояснение нетривиального алгоритма  Пример использования метода или класса 38/79
  53. Полезные комментарии  Объяснение сути и причин неочевидного или спорного

    решения  Пояснение «контракта» метода или класса, если «контракт» не очевиден из его сигнатуры  Пояснение нетривиального алгоритма  Пример использования метода или класса 38/79 Вместо написания комментариев попробуйте сначала написать понятный код
  54. Информативный интерфейс Интерфейс, из которого очевидным образом ясен «контракт» класса

    или метода  Пример «неинформативного» интерфейса package com.mycompany.eshop.adminconsole.dataaccess; interface Actions { long importCatalog(); long export(Date date); void cleanup(int mode); } 39/79
  55. Информативный интерфейс Интерфейс, из которого очевидным образом ясен «контракт» класса

    или метода  Пример «неинформативного» интерфейса package com.mycompany.eshop.adminconsole.dataaccess; interface Actions { long importCatalog(); long export(Date date); void cleanup(int mode); } 39/79
  56. Информативный интерфейс Интерфейс, из которого очевидным образом ясен «контракт» класса

    или метода  Пример «неинформативного» интерфейса package com.mycompany.eshop.adminconsole.dataaccess; interface Actions { long importCatalog(); long export(Date date); void cleanup(int mode); } 39/79
  57. Неинформативный интерфейс package com.mycompany.eshop.adminconsole.dataaccess; /** * Действия администратора в БД

    в части интеграции с EBay */ interface Actions { long importCatalog(); long export(Date date); void cleanup(int mode); } 40/79
  58. Неинформативный интерфейс package com.mycompany.eshop.adminconsole.dataaccess; /** * Действия администратора в БД

    в части интеграции с EBay */ interface Actions { long importCatalog(); long export(Date date); void cleanup(int mode); } 40/79
  59. Неинформативный интерфейс package com.mycompany.eshop.adminconsole.dataaccess; /** * Действия администратора в БД

    в части интеграции с EBay */ interface Actions { long importCatalog(); long export(Date date); void cleanup(int mode); } 40/79
  60. Неинформативный интерфейс package com.mycompany.eshop.adminconsole.dataaccess; /** * Объект с таким интерфейсом

    выполняет действия с БД * системы для интеграции с каталогом товаров на Ebay. * Действия запускаются администратором системы. */ interface Actions { /** импорт каталога с Ebay */ long importCatalog(); /** Экспорт каталога на Ebay на заданную дату */ long export(Date date); /** Удаление временных данных импорта и экспорта */ void cleanup(int mode); } 41/79
  61. Неинформативный интерфейс package com.mycompany.eshop.adminconsole.dataaccess; /** * Объект с таким интерфейсом

    выполняет действия с БД * системы для интеграции с каталогом товаров на Ebay. * Действия запускаются администратором системы. */ interface Actions { /** импорт каталога с Ebay */ long importCatalog(); /** Экспорт каталога на Ebay на заданную дату */ long export(Date date); /** Удаление временных данных импорта и экспорта */ void cleanup(int mode); } 41/79
  62. Информативный интерфейс package com.mycompany.eshop.adminconsole.ebay; interface EbayCatalogImporter { ImportResult importIntoDatabase(); void

    cleanupImportStageData(); } interface EbayCatalogExporter { ExportResult exportToEbay(Date catalogStateOn); void cleanupExportStageData(); } 42/79
  63. Информативный интерфейс package com.mycompany.eshop.adminconsole.ebay; interface EbayCatalogImporter { ImportResult importIntoDatabase(); void

    cleanupImportStageData(); } interface EbayCatalogExporter { ExportResult exportToEbay(Date catalogStateOn); void cleanupExportStageData(); } 42/79
  64. Программируйте с использованием языка, а не на языке  Выражайте

    логику приложения естественным языком с помощью языка программирования  Сначала решите, что хотите выразить. Затем воплотите это на языке программирования 43/79
  65. Программируйте с использованием языка, а не на языке public Invoice

    processNewOrder(Order order) { Stock.Reservation stockReservation = // (1) резервируем stock.reserve(order.getItems()); // товар на складе if (stockReservation.isSuccessful()) { // (2) выявляем риски Risks risks = riskRules.evaluate(order); // (3) ставим заказ в очередь на обработку OrderNumber ordNum = ordersHandling.enqueue(order, risks); // (4) подтверждаем резервирование товара на складе stock.confirm(stockReservation, ordNum); // (5) рассчитываем скидку Discount discount = discountRules.apply(order); // (6) выставляем счёт return invoiceFactory.create(order, discount); } ... 44/79
  66. Программируйте с использованием языка, а не на языке public Invoice

    processNewOrder(Order order) { Stock.Reservation stockReservation = // (1) резервируем stock.reserve(order.getItems()); // товар на складе if (stockReservation.isSuccessful()) { // (2) выявляем риски Risks risks = riskRules.evaluate(order); // (3) ставим заказ в очередь на обработку OrderNumber ordNum = ordersHandling.enqueue(order, risks); // (4) подтверждаем резервирование товара на складе stock.confirm(stockReservation, ordNum); // (5) рассчитываем скидку Discount discount = discountRules.apply(order); // (6) выставляем счёт return invoiceFactory.create(order, discount); } ... 44/79 Комментарии мешают :(
  67. Программируйте с использованием языка, а не на языке public Invoice

    processNewOrder(Order order) { Stock.Reservation stockReservation = stock.reserve(order.getItems()); if (stockReservation.isSuccessful()) { Risks risks = riskRules.evaluate(order); OrderNumber ordNum = ordersHandling.enqueue(order, risks); stock.confirm(stockReservation, ordNum); Discount discount = discountRules.apply(order); return invoiceFactory.create(order, discount); } ... 45/79
  68. Программируйте с использованием языка, а не на языке public Invoice

    processNewOrder(Order order) { Stock.Reservation stockReservation = stock.reserve(order.getItems()); if (stockReservation.isSuccessful()) { Risks risks = riskRules.evaluate(order); OrderNumber ordNum = ordersHandling.enqueue(order, risks); stock.confirm(stockReservation, ordNum); Discount discount = discountRules.apply(order); return invoiceFactory.create(order, discount); } ... 45/79
  69. Программируйте с использованием языка, а не на языке public Invoice

    processNewOrder(Order order) { Stock.Reservation stockReservation = stock.reserve(order.getItems()); if (stockReservation.isSuccessful()) { Risks risks = riskRules.evaluate(order); OrderNumber ordNum = ordersHandling.enqueue(order, risks); stock.confirm(stockReservation, ordNum); Discount discount = discountRules.apply(order); return invoiceFactory.create(order, discount); } ... 45/79 Почему метод «резервировать» дан «складу», а не «товару»?
  70. Макеты кода  Псевдокод  Заход с точки зрения «клиента»

    класса class ReportingController { def tasksManager; def reportsService; def scheduleReportBuilding(def parameters) { return tasksManager.submit (task: { reportsService.buildReport(parameters); }, timeout: 60); } } 46/79
  71. Макеты кода  Псевдокод  Заход с точки зрения «клиента»

    класса class ReportingController { def tasksManager; def reportsService; def scheduleReportBuilding(def parameters) { return tasksManager.submit (task: { reportsService.buildReport(parameters); }, timeout: 60); } } 46/79
  72. Макеты кода  Псевдокод  Заход с точки зрения «клиента»

    класса class ReportingController { def tasksManager; def reportsService; def scheduleReportBuilding(def parameters) { return tasksManager.submit (task: { reportsService.buildReport(parameters); }, timeout: 60); } } 46/79 Часто макеты кода пишутся еще до завершения декомпозиции, это часть процесса проектирования
  73. Будьте проще при выборе реализации решения  «Не боги горшки

    обжигают»  Не все разработчики владеют специфическими знаниями  Лучше ориентироваться на «усредненного» разработчика 47/79 Rocket Science
  74. Решайте задачу на подходящем уровне абстракции  Решайте задачу в

    ее терминах!  Пример сценария реализации  Пользователь выбирает коня из списка на форме «Сферические кони»  Затем нажимает кнопку «Коня в вакуум!» на той же форме  Сферический конь помещается в вакуум 48/79
  75. Сферический конь в вакууме» 49/79 UI / Presentation Application Domain

    GUI «Сферические кони» и его контроллер
  76. Сферический конь в вакууме» 49/79 UI / Presentation Application Domain

    GUI «Сферические кони» и его контроллер Операция «Поместить сферического коня в вакуум»
  77. Сферический конь в вакууме» 49/79 UI / Presentation Application Domain

    GUI «Сферические кони» и его контроллер Операция «Поместить сферического коня в вакуум» Вакуум :)
  78. Сферический конь в вакууме» 49/79 UI / Presentation Application Domain

    GUI «Сферические кони» и его контроллер Операция «Поместить сферического коня в вакуум» Вакуум :)
  79. «Сферический конь в вакууме» package com.mycompany.sphericalhorses.presentation; … protected void onMoveToVacuumBtnClick()

    { sphericalHorsesScenarios.moveToVacuum( getSelectedHorse()); } package com.mycompany.sphericalhorses.application; … public class SphericalHorsesScenarios { public void moveToVacuum(Horse horse) { Vacuum vacuum = VacuumFactory.create(defaultConfig()); horse.moveTo(vacuum); } 50/79
  80. «Сферический конь в вакууме» package com.mycompany.sphericalhorses.presentation; … protected void onMoveToVacuumBtnClick()

    { sphericalHorsesScenarios.moveToVacuum( getSelectedHorse()); } package com.mycompany.sphericalhorses.application; … public class SphericalHorsesScenarios { public void moveToVacuum(Horse horse) { Vacuum vacuum = VacuumFactory.create(defaultConfig()); horse.moveTo(vacuum); } 50/79
  81. «Сферический конь в вакууме» package com.mycompany.sphericalhorses.presentation; … protected void onMoveToVacuumBtnClick()

    { sphericalHorsesScenarios.moveToVacuum( getSelectedHorse()); } package com.mycompany.sphericalhorses.application; … public class SphericalHorsesScenarios { public void moveToVacuum(Horse horse) { Vacuum vacuum = VacuumFactory.create(defaultConfig()); horse.moveTo(vacuum); } 50/79
  82. «Сферический конь в вакууме» package com.mycompany.sphericalhorses.presentation; … protected void onMoveToVacuumBtnClick()

    { sphericalHorsesScenarios.moveToVacuum( getSelectedHorse()); } package com.mycompany.sphericalhorses.application; … public class SphericalHorsesScenarios { public void moveToVacuum(Horse horse) { Vacuum vacuum = VacuumFactory.create(defaultConfig()); horse.moveTo(vacuum); } 50/79 А это что? Этого нет в сценарии!
  83. Соглашения о кодировании  Правила именования методов, переменных, классов; суффиксы,

    префиксы, …  Единообразное оформление, форматирование исходного кода, … 51/79
  84. Соглашения о кодировании  Правила именования методов, переменных, классов; суффиксы,

    префиксы, …  Единообразное оформление, форматирование исходного кода, … 51/79 Круто, но это же банально…
  85. Соглашения о кодировании «+»  Два следующих интерфейса написаны по-разному,

    но функционально они совершенно одинаковы interface ClientsRepository { Client findClientBySSN(String SSN); Client getClient(@NotNull Long id); List<Client> search(SearchCriteria criteria); Collection<Client> findClients(ClientSpecification spec); Client[] loadClients(String ITN, String nameLike) throws extworks.xml.NoObjectsFoundException; Client getClient(String ITN); } interface ClientsRepository { Client get(long clientId) throws ClientNotFoundException; Client findBySSN(String SSN); Client findByITN(String ITN); List<Client> findBySpecification(ClientSpecification spec); } 52/79 1 2
  86. Соглашения о кодировании «+» interface ClientsRepository { Client findClientBySSN(String SSN);

    Client getClient(@NotNull Long id); List<Client> search(SearchCriteria criteria); Collection<Client> findClients(ClientSpecification spec); Client[] loadClients(String ITN, String nameLike) throws extworks.xml.NoObjectsFoundException; Client getClient(String ITN); } 53/79 1 Они ненавидят свою работу
  87. Соглашения о кодировании «+» interface ClientsRepository { Client findClientBySSN(String SSN);

    Client getClient(@NotNull Long id); List<Client> search(SearchCriteria criteria); Collection<Client> findClients(ClientSpecification spec); Client[] loadClients(String ITN, String nameLike) throws extworks.xml.NoObjectsFoundException; Client getClient(String ITN); } 53/79 1 Они ненавидят свою работу
  88. Соглашения о кодировании «+» interface ClientsRepository { Client findClientBySSN(String SSN);

    Client getClient(@NotNull Long id); List<Client> search(SearchCriteria criteria); Collection<Client> findClients(ClientSpecification spec); Client[] loadClients(String ITN, String nameLike) throws extworks.xml.NoObjectsFoundException; Client getClient(String ITN); } 53/79 1 Они ненавидят свою работу
  89. Соглашения о кодировании «+» interface ClientsRepository { Client get(long clientId)

    throws ClientNotFoundException; Client findBySSN(String SSN); Client findByITN(String ITN); List<Client> findBySpecification(ClientSpecification spec); } 54/79 2
  90. Соглашения о кодировании «+» interface ClientsRepository { Client get(long clientId)

    throws ClientNotFoundException; Client findBySSN(String SSN); Client findByITN(String ITN); List<Client> findBySpecification(ClientSpecification spec); } 54/79 2
  91. Соглашения о кодировании «+» interface ClientsRepository { Client get(long clientId)

    throws ClientNotFoundException; Client findBySSN(String SSN); Client findByITN(String ITN); List<Client> findBySpecification(ClientSpecification spec); } 54/79 2
  92. Соглашения о кодировании «+» interface ClientsRepository { Client get(long clientId)

    throws ClientNotFoundException; Client findBySSN(String SSN); Client findByITN(String ITN); List<Client> findBySpecification(ClientSpecification spec); } 54/79 2 Информативный интерфейс
  93. Соглашения о кодировании «+» 55/79 # Конфигурационные опции для службы

    отправки уведомлений по E-mail userName=jsmith password=xWelcome@797 mailServerHost=acme.com 1
  94. Соглашения о кодировании «+» 55/79 # Конфигурационные опции для службы

    отправки уведомлений по E-mail userName=jsmith password=xWelcome@797 mailServerHost=acme.com EmailNotificationsService.emailserver.userName=jsmith EmailNotificationsService.emailserver.password=xWelcome@797 EmailNotificationsService.emailserver.host=acme.com 1 2
  95. Соглашения о кодировании «+» 55/79 # Конфигурационные опции для службы

    отправки уведомлений по E-mail userName=jsmith password=xWelcome@797 mailServerHost=acme.com EmailNotificationsService.emailserver.userName=jsmith EmailNotificationsService.emailserver.password=xWelcome@797 EmailNotificationsService.emailserver.host=acme.com AdminService.emailserver.userName=jdoe AdminService.emailserver.password=xWelcome@797 AdminService.emailserver.host=acme.admin.com 1 2 3
  96. Соглашения о кодировании «+» 55/79 # Конфигурационные опции для службы

    отправки уведомлений по E-mail userName=jsmith password=xWelcome@797 mailServerHost=acme.com EmailNotificationsService.emailserver.userName=jsmith EmailNotificationsService.emailserver.password=xWelcome@797 EmailNotificationsService.emailserver.host=acme.com AdminService.emailserver.userName=jdoe AdminService.emailserver.password=xWelcome@797 AdminService.emailserver.host=acme.admin.com 1 2 3
  97. Мысли вслух  Простой код сделать сложным – легко. Сложный

    код сделать простым – очень сложно  Даже сложную задачу следует попытаться решить простым способом 56/79
  98. Где мы?  Декомпозиция и принципы ООП  Написание «понятного»

    кода  Перерыв  Программный продукт, удобный для разработчика  Разработка «велосипедов» 58/79
  99. Удобный инструментарий может стать залогом успеха  Фреймворки, библиотеки, IDE

     Сборка, репозитарии артефактов и Continuous Integration  Типовое решение  Удобный инструмент 60/79
  100. «Типовое решение» делает жизнь разработчика удобнее  Проблема  Одна

    команда, но много концептуально похожих и связных приложений 61/79
  101. «Типовое решение» делает жизнь разработчика удобнее  Проблема  Одна

    команда, но много концептуально похожих и связных приложений  Решение  Унификация реализации приложений (архитектура, фреймворки, инфраструктура, …) 61/79
  102. «Типовое решение» делает жизнь разработчика удобнее  Проблема  Одна

    команда, но много концептуально похожих и связных приложений  Решение  Унификация реализации приложений (архитектура, фреймворки, инфраструктура, …)  Преимущество  Привычная среда для разработчиков 61/79
  103. Замените инструмент на более удобный  Как было? 63/79 «Движок»

    Хранилище плагинов JS JS JS JS JavaScript-файлы. Каждый плагин – «фабрика», создающая сложный объект определенного типа
  104. Замените инструмент на более удобный  Как было? 63/79 «Движок»

    Хранилище плагинов JS JS JS JS JavaScript-файлы. Каждый плагин – «фабрика», создающая сложный объект определенного типа Движок визуализирует сложные объекты, созданные плагинами
  105. Замените инструмент на более удобный  Как было?  Отсутствовала

    проверка этапа компиляции  Валидатор кода IDE вечно «ругался»  Не было возможности использовать отладчик  Не было IntelliSense  Тяжело разрабатывать и исправлять ошибки 63/79 «Движок» Хранилище плагинов JS JS JS JS JavaScript-файлы. Каждый плагин – «фабрика», создающая сложный объект определенного типа Движок визуализирует сложные объекты, созданные плагинами
  106. Замените инструмент на более удобный  Как было?  Отсутствовала

    проверка этапа компиляции  Валидатор кода IDE вечно «ругался»  Не было возможности использовать отладчик  Не было IntelliSense  Тяжело разрабатывать и исправлять ошибки 63/79 «Движок» Хранилище плагинов JS JS JS JS JavaScript-файлы. Каждый плагин – «фабрика», создающая сложный объект определенного типа Движок визуализирует сложные объекты, созданные плагинами
  107. Замените инструмент на более удобный  Как стало? 64/79 «Движок»

    Хранилище плагинов JS JS JS G S JavaScript заменили на Groovy-классы
  108. Замените инструмент на более удобный  Как стало?  Появилась

    проверка уровня компиляции  Валидатор кода IDE стал «доволен»  Отладка Groovy-кода в IDE хорошо работала  Ура, есть IntelliSense!  В разы повысилась производительность разработки 64/79 «Движок» Хранилище плагинов JS JS JS G S JavaScript заменили на Groovy-классы
  109. Замените инструмент на более удобный  Как стало?  Появилась

    проверка уровня компиляции  Валидатор кода IDE стал «доволен»  Отладка Groovy-кода в IDE хорошо работала  Ура, есть IntelliSense!  В разы повысилась производительность разработки 64/79 «Движок» Хранилище плагинов JS JS JS G S JavaScript заменили на Groovy-классы
  110. Заранее думайте о диагностике и поиске причин сбоев  Очень

    часто файловый журнал (LOG) – единственный способ узнать, что происходит на Production  Проектируйте вывод диагностической информации в журнал  Используйте журнал и для сбора статистики  Периодически анализируйте журналы 65/79
  111. Заранее думайте о диагностике и поиске причин сбоев  Очень

    часто файловый журнал (LOG) – единственный способ узнать, что происходит на Production  Проектируйте вывод диагностической информации в журнал  Используйте журнал и для сбора статистики  Периодически анализируйте журналы 65/79 Есть связанная тема «Обработка ошибок», но она заслуживает отдельного обсуждения
  112. Факторы хороших журналов  Не надо «лить» в журнал «воду»!

    66/79 Трезво оцените полезность каждой записи в журнале
  113. Факторы хороших журналов  Не надо «лить» в журнал «воду»!

     Учет объема файлов журналов  Вдумчивое использование уровней критичности (DEBUG, INFO, WARN, ERROR)  Запись нескольких файлов журналов  Отдельная конфигурация для отладки, тестирования и «боевых условий»  Возможность перенастройки в «боевых условиях» 66/79 Трезво оцените полезность каждой записи в журнале
  114. Отчет об ошибке  Что обычно включают в отчет об

    ошибке?  Версию приложения  Версию ОС и (или) виртуальной машины  Версию веб-браузера  Версию драйвера БД  Детальную информацию об ошибке 67/79
  115. Отчет об ошибке  Что обычно включают в отчет об

    ошибке?  Версию приложения  Версию ОС и (или) виртуальной машины  Версию веб-браузера  Версию драйвера БД  Детальную информацию об ошибке  Все это справедливо как для серверных, так и для клиентских приложений 67/79
  116. Отчет об ошибке  Что обычно включают в отчет об

    ошибке?  Версию приложения  Версию ОС и (или) виртуальной машины  Версию веб-браузера  Версию драйвера БД  Детальную информацию об ошибке  Все это справедливо как для серверных, так и для клиентских приложений 67/79 Можно также вложить информацию об активности пользователя
  117. Тесты должны служить долго  Модульные, интеграционные тесты  Проектируйте

    тесты  Следите за качеством исходного кода самого теста  Документируйте тесты 68/79
  118. Тесты должны служить долго  Модульные, интеграционные тесты  Проектируйте

    тесты  Следите за качеством исходного кода самого теста  Документируйте тесты 68/79 Не забываем про недостатки документирования и комментариев в исходном коде
  119. Избавляйтесь от «хлама»  «Хлам» – неиспользуемый устаревший код или

    код, написанный «на всякий случай», а также конфигурации, документация и др.  Отвлекает внимание  Мешает увидеть истинное положение дел  Вводит в заблуждение  Делает работу разработчиков менее удобной 69/79
  120. Не откладывайте на завтра  Улучшение форматирования кода  Улучшение

    имен переменных, методов и их параметров, классов, …  Уничтожение «бестолковых» комментариев или их улучшение 70/79 Вспоминайте народную мудрость: «Чисто не там, где убирают. Чисто там, где не сорят»
  121. Не откладывайте на завтра. Более емкий рефакторинг? Да, но учитывайте…

     Наличие простых способов проверить, не сломалось ли что-то  Например, автоматические тесты  Организационные правила в проекте  Например, любые улучшения и рефакторинги – это отдельные задачи 71/79
  122. Где мы?  Декомпозиция и принципы ООП  Написание понятного

    кода  Перерыв  Программный продукт, удобный для разработчика  Разработка «велосипедов» 72/79
  123. Каким должен быть «велосипед»?  «Велосипед» – это «доморощенные» инструменты

    и технологии  Каким должен быть «велосипед»?  Полезным  Удобным  «Прозрачным»  Таким, чтобы его было легко и просто «выбросить» :) 74/79
  124. Каким должен быть «велосипед»?  «Велосипед» – это «доморощенные» инструменты

    и технологии  Каким должен быть «велосипед»?  Полезным  Удобным  «Прозрачным»  Таким, чтобы его было легко и просто «выбросить» :) 74/79 Почему?
  125. Почему «легко выбрасываемый» «велосипед» – это хорошо?  «Потомки» скажут

    вам «Спасибо!», если у них возникнет идея получше :)  Он хорошо спроектирован! 75/79
  126. Пример «велосипеда» return SQLHelper.executeForList( dataSource, // JDBC Data Source new

    String[] {"order_id","order_date"}, // Columns "ORDERS", // Table "status = ?", Order.Status.PENDING, // Conditions orderColum + " " + orderDirection, // Sort order 5, 20, // Page index,size SQLHelper.rowMapper(Order.class)); SqlQuery query = new SqlQueryBuilder() .baseSql("SELECT order_id, order_date " + "FROM orders WHERE status = ?", Order.Status.PENDING) .addSortOrder(orderColumn, orderDirection) .pageIndex(5).pageSize(20) .build(); return jdbcTemplate.queryForList(query.sql(), query.parameters(), new OrderRowMapper()); 76/79 1 2
  127. Пример «велосипеда» return SQLHelper.executeForList( dataSource, // JDBC Data Source new

    String[] {"order_id","order_date"}, // Columns "ORDERS", // Table "status = ?", Order.Status.PENDING, // Conditions orderColum + " " + orderDirection, // Sort order 5, 20, // Page index,size SQLHelper.rowMapper(Order.class)); SqlQuery query = new SqlQueryBuilder() .baseSql("SELECT order_id, order_date " + "FROM orders WHERE status = ?", Order.Status.PENDING) .addSortOrder(orderColumn, orderDirection) .pageIndex(5).pageSize(20) .build(); return jdbcTemplate.queryForList(query.sql(), query.parameters(), new OrderRowMapper()); 76/79 1 2
  128. Читайте книги  Стив Макконнелл «Совершенный код»  Крэг Ларман

    «Применение UML 2.0 и шаблонов проектирования»  Эрик Эванс «Предметно-ориентированное проектирование. Структуризация сложных программных систем» 78/79