
 Если вы читали первый блог в серии, вы, возможно, помните, что я первоначально сказал, что мне нужен класс Report и что «если вы посмотрите на код, вы не найдете класс с именем Report, он был переименован в Results и подвергся рефакторингу». создать интерфейс Formatter, классы TextFormatter и HtmlFormatter вместе с интерфейсом Publisher и классом EmailPublisher ».  В этом блоге рассказывается о процессе разработки, освещаются причины рефакторинга, и как я пришел к окончательной реализации. 
  Если вы читаете дальше, вы можете подумать, что приведенная ниже логика проектирования несколько надумана.  Это потому что это так.  Фактический процесс перехода от класса Report классу Results , интерфейсам Formatter и Publisher вместе с их реализациями, вероятно, занял всего несколько секунд.  однако, написание всего этого заняло некоторое время.  История дизайна выглядит так … 
  Если у вас есть класс с именем Report как вы определяете его ответственность?  Вы можете сказать что-то вроде этого: «Класс Report отвечает за генерацию отчета об ошибке.  Кажется, это соответствует принципу единой ответственности , поэтому мы должны быть в порядке … или мы?  Утверждение, что «Отчет» отвечает за создание отчета, довольно тавтологично.  Это все равно что сказать, что стол отвечает за то, чтобы быть столом, это ничего не говорит нам.  Нам нужно разбить это дальше.  Что означает «создание отчета»?  Какие шаги предпринимаются?  Размышляя об этом, для составления отчета нам необходимо: 
- Маршалл данные ошибки.
- отформатировать данные об ошибках в читаемый документ.
- опубликовать отчет в известном месте назначения.
  Если вы включите все это в определение ответственности класса Report , вы получите что-то вроде этого: 
«Класс
Reportотвечает за сбор данных об ошибках, их форматирование в удобочитаемый документ и публикацию отчета в известном месте назначения».
  Очевидно, что это нарушает принцип единой ответственности, потому что класс Report имеет три обязанности вместо одной;  Вы можете сказать, используя слово «и».  Это действительно означает, что у нас есть три класса: один для обработки результатов, один для форматирования отчета и один для публикации отчета, и эти три слабосвязанных класса должны сотрудничать, чтобы получить этот отчет. 
Если вы посмотрите на первоначальные требования, пункты 6 и 7 сказали:
| 1 2 | 6. When all the files have been checked, format the report ready forpublishing.7. Publish the report using email or some other technique. | 
Требование 6 довольно простое и конкретное, мы знаем, что нам нужно отформатировать отчет. В реальном проекте вам придется либо придумать формат самостоятельно, либо спросить клиента, что они хотели видеть в своем отчете.
Требование 7 несколько более проблематично. Первая часть в порядке, там написано «публиковать отчет по электронной почте», и с Spring это не проблема. Вторая очень плохо написана: какая другая техника? Это требуется для этого первого выпуска? Если это был проект в реальном мире, которым вы зарабатываете на жизнь, тогда вам нужно задать несколько вопросов — очень громко, если это необходимо. Это потому, что не поддающееся количественному определению требование будет влиять на временные рамки, что также может сделать вас плохо выглядящим.
Опрос плохо сформулированных требований или историй является ключевым навыком, когда дело доходит до хорошего разработчика. Если требование неверное или расплывчатое, никто не поблагодарит вас, если вы просто придумали и интерпретировали его по-своему. То, как вы формулируете свой вопрос, — это другое дело. Обычно хорошей идеей является быть профессионалом в этом вопросе и говорить что-то вроде: «извините, у вас есть пять минут, чтобы объяснить мне эту историю, я ее не понимаю». Есть только несколько ответов, которые вы получите:
- «Не мешай мне сейчас, возвращайся позже…»
- «О, да, это ошибка в требованиях — спасибо, что заметил, я разберусь».
- «Конечный пользователь был действительно расплывчатым, я свяжусь с ними и уточню, что они имели в виду».
- «Я понятия не имею — догадайся …»
- «Это требование означает, что вам нужно сделать X, Y, Y…»
… и не забудьте записать свои нерешенные вопросы о требованиях и преследовать их: чья-то бездеятельность может угрожать вашим срокам.
В данном конкретном случае пояснение будет состоять в том, что я собираюсь добавить дополнительные методы публикации в более поздних блогах и что я хочу, чтобы код, разработанный для расширения, был прост, что на простом английском языке означает использование интерфейсов
  Диаграмма выше показывает, что первоначальная идея класса Report была разделена на три части: Results , Formatter и Publisher .  Любой, кто знаком с шаблонами проектирования, заметит, что я использовал шаблон стратегии для внедрения реализаций Formatter и Publisher в класс Results .  Это позволяет мне сказать классу Results generate() отчет без того, чтобы класс Results ничего не знал о отчете, его структуре или о том, где он собирается. 
| 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 | @ServicepublicclassResults {   privatestaticfinalLogger logger = LoggerFactory.getLogger(Results.class);   privatefinalMap<String, List<ErrorResult>> results = newHashMap<String, List<ErrorResult>>();   /**    * Add the next file found in the folder.    *    * @param filePath    *            the path + name of the file    */  publicvoidaddFile(String filePath) {     Validate.notNull(filePath);     Validate.notBlank(filePath, "Invalid file/path");     logger.debug("Adding file {}", filePath);     List<ErrorResult> list = newArrayList<ErrorResult>();     results.put(filePath, list);   }   /**    * Add some error details to the report.    *    * @param path    *            the file that contains the error    * @param lineNumber    *            The line number of the error in the file    * @param lines    *            The group of lines that contain the error    */  publicvoidaddResult(String path, intlineNumber, List<String> lines) {     Validate.notBlank(path, "Invalid file/path");     Validate.notEmpty(lines);     Validate.isTrue(lineNumber > 0, "line numbers must be positive");     List<ErrorResult> list = results.get(path);     if(isNull(list)) {       addFile(path);       list = results.get(path);     }     ErrorResult errorResult = newErrorResult(lineNumber, lines);     list.add(errorResult);     logger.debug("Adding Result: {}", errorResult);   }   privatebooleanisNull(Object obj) {     returnobj == null;   }   publicvoidclear() {     results.clear();   }   Map<String, List<ErrorResult>> getRawResults() {     returnCollections.unmodifiableMap(results);   }   /**    * Generate a report    *    * @return The report as a String    */  public<T> voidgenerate(Formatter formatter, Publisher publisher) {     T report = formatter.format(this);     if(!publisher.publish(report)) {       logger.error("Failed to publish report");     }   }   publicclassErrorResult {     privatefinalintlineNumber;     privatefinalList<String> lines;     ErrorResult(intlineNumber, List<String> lines) {       this.lineNumber = lineNumber;       this.lines = lines;     }     publicintgetLineNumber() {       returnlineNumber;     }     publicList<String> getLines() {       returnlines;     }     @Override    publicString toString() {       return"LineNumber: "+ lineNumber + "\nLines:\n"+ lines;     }   } } | 
  Взяв сначала код Results , вы увидите, что есть четыре открытых метода;  три, отвечающие за сортировку данных результата, и одна, которая создает отчет: 
-  addFile(…)
-  addResults(…)
-  clear(…)
-  generate(…)
  Первые три метода, описанные выше, управляют картой хэша Map<String, List<ErrorResult>> results Result s Map<String, List<ErrorResult>> results .  Ключи на этой карте — это имена любых файлов журналов, которые FileLocator класс FileLocator , в то время как значения являются ErrorResult компонентов ErrorResult .  ErrorResult компонент ErrorResult — это простой внутренний класс EJB-компонента, который используется для группировки деталей всех найденных ошибок. 
  addFile() — это простой метод, который используется для регистрации файла в классе Results .  Он генерирует запись в карте results и создает пустой список.  Если это остается пустым, то мы можем сказать, что этот файл без ошибок.  Вызов этого метода не является обязательным. 
  addResult() — это метод, который добавляет новый результат ошибки на карту.  После проверки входных аргументов с помощью org.apache.commons.lang3.Validate он проверяет, присутствует ли этот файл в карте результатов.  Если это не так, он создает новую запись, прежде чем, наконец, создать новый компонент ErrorResult и добавить его в соответствующий List на Map . 
  Метод clear() очень прост: он удалит текущее содержимое карты results . 
  Оставшийся открытый метод generate(…) отвечает за генерацию окончательного отчета об ошибке.  Это наша реализация шаблона стратегии, принимающая два аргумента: реализацию Formatter реализацию Publisher.  Код очень прост, так как нужно рассмотреть только три строки.  Первая строка вызывает реализацию Formatter для форматирования отчета, вторая публикует отчет, а третья строка регистрирует любую ошибку в случае сбоя при создании отчета.  Обратите внимание, что это универсальный метод (как показано <T>, прикрепленным к сигнатуре метода).  В этом случае, единственное «Gotcha», который нужно остерегаться, это то, что этот «T» должен быть одинакового типа для обоих 
  Реализация Formatter реализация Publisher .  Если это не так, то все рухнет. 
| 1 2 3 4 | publicinterfaceFormatter {   public<T> T format(Results report); } | 
  Formatter представляет собой интерфейс с одним методом: public <T> T format(Results report) .  Этот метод принимает класс Report в качестве аргумента и возвращает отформатированный отчет любого типа, который вам нравится 
| 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 | @ServicepublicclassTextFormatter implementsFormatter {   privatestaticfinalString RULE = "\n==================================================================================================================\n";   @SuppressWarnings("unchecked")   @Override  public<T> T format(Results results) {     StringBuilder sb = newStringBuilder(dateFormat());     sb.append(RULE);     Set<Entry<String, List<ErrorResult>>> entries = results.getRawResults().entrySet();     for(Entry<String, List<ErrorResult>> entry : entries) {       appendFileName(sb, entry.getKey());       appendErrors(sb, entry.getValue());     }     return(T) sb.toString();   }   privateString dateFormat() {     SimpleDateFormat df = newSimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");     returndf.format(Calendar.getInstance().getTime());   }   privatevoidappendFileName(StringBuilder sb, String fileName) {     sb.append("File:  ");     sb.append(fileName);     sb.append("\n");   }   privatevoidappendErrors(StringBuilder sb, List<ErrorResult> errorResults) {     for(ErrorResult errorResult : errorResults) {       appendErrorResult(sb, errorResult);     }   }   privatevoidappendErrorResult(StringBuilder sb, ErrorResult errorResult) {     addLineNumber(sb, errorResult.getLineNumber());     addDetails(sb, errorResult.getLines());     sb.append(RULE);   }   privatevoidaddLineNumber(StringBuilder sb, intlineNumber) {     sb.append("Error found at line: ");     sb.append(lineNumber);     sb.append("\n");   }   privatevoidaddDetails(StringBuilder sb, List<String> lines) {     for(String line : lines) {       sb.append(line);       // sb.append("\n");     }   } } | 
  Это действительно скучный код.  Все, что он делает, — это создает отчет с помощью StringBuilder , аккуратно добавляя текст, пока отчет не будет завершен.  Там есть только интересующий объект, и он находится в третьей строке кода в format(…) : 
| 1 |     Set<Entry<String, List<ErrorResult>>> entries = results.getRawResults().entrySet(); | 
  Это учебный пример того, что представляет собой редко используемый видимость пакетов Java.  Класс Results класс TextFormatter должны совместно сгенерировать отчет.  Для этого коду TextFormatter необходим доступ к данным класса Results ;  однако эти данные являются частью внутренней работы класса Result и не должны быть общедоступными.  Следовательно, имеет смысл сделать эти данные доступными через package private метод package private , а это означает, что только те классы, которым нужны данные, чтобы взять на себя распределенную ответственность, могут получить доступ к ним. 
  Заключительная часть создания отчета — публикация отформатированных результатов.  Это снова делается с использованием шаблона стратегии;  Второй аргумент метода generate(…) класса Report — это реализация интерфейса Publisher : 
| 1 2 3 4 | publicinterfacePublisher {   public<T> booleanpublish(T report); } | 
  Это также содержит единственный метод: public <T> boolean publish(T report);  ,  Этот универсальный метод принимает аргумент report типа ‘T’, возвращая true, если отчет успешно опубликован. 
Как насчет реализации (й)? этого класса? Первая реализация использует классы электронной почты Spring и станет темой моего следующего блога, который вскоре будет опубликован…
- Код для этого блога доступен на Github по адресу: https://github.com/roghughe/captaindebug/tree/master/error-track .
Если вы хотите посмотреть другие блоги этой серии, загляните сюда …
- Отслеживание исключений приложений с помощью Spring
- Отслеживание исключений в Spring — часть 2 — шаблон делегата
| Ссылка: | Отчеты по отслеживанию ошибок — часть 3 — Стратегия и пакет, конфиденциальный от нашего партнера по JCG Роджера Хьюза в блоге Captain Debug’s Blog . | 
