После пары недель занятий дзюдо моему сыну стало скучно. Он жаловался, что ничего не учил, потому что продолжал делать одно и то же снова и снова.
Это не просто маленькие дети, которые путают обучение и делать новые вещи. Например, сколько разработчиков программного обеспечения сталкиваются с проблемой преднамеренной практики , выполняя каты или посещая додзё ?
Может показаться глупым повторять упражнения, которые вы уже делали много раз, но это не так. Это единственный способ стать черным поясом в своей области. И помните, что мастерство является одним из трех внутренних мотиваторов (другие — это автономия и цель).
Практика означает замедление и перемещение внимания от результата к процессу. Лучше всего использовать простые упражнения, которые вы можете выполнить за ограниченное время, чтобы вы могли выполнять одно и то же упражнение несколько раз.
Я обнаружил, что практически всегда узнаю что-то новое, когда практикуюсь. Это не потому, что я забыл, как решить проблему с прошлого раза, а потому, что с тех пор я узнал что-то новое и, таким образом, вижу мир новыми глазами.
Например, после выхода Java 8 я пытался использовать новые потоковые классы, чтобы помочь перейти к более функциональному стилю программирования. Это изменило мой взгляд на старые проблемы, такие как FizzBuzz .
Давайте посмотрим это в действии. Конечно, я начинаю с добавления теста:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
|
+ package remonsinnema.blog.fizzbuzz;++ import static org.junit.Assert.assertEquals;++ import org.junit.Test;+++ public class WhenFizzingAndBuzzing {++ private final FizzBuzz fizzbuzz = new FizzBuzz();++ @Test+ public void shouldReplaceWithFizzAndBuzz() {+ assertEquals(“1”, “1”, fizzbuzz.get(1));+ }++ } |
В этом тесте используется форма модульного тестирования « Когда… Следует», которая помогает сосредоточиться на поведении, а не на деталях реализации. Я позволил Eclipse сгенерировать код, необходимый для компиляции:
|
01
02
03
04
05
06
07
08
09
10
|
+ package remonsinnema.blog.fizzbuzz;+++ public class FizzBuzz {++ public String get(int i) {+ return null;+ }++ } |
Простейший код, который делает тестовый проход, состоит в том, чтобы подделать его :
|
1
2
3
4
5
6
7
|
package remonsinnema.blog.fizzbuzz; public class FizzBuzz { public String get(int i) {– return null;+ return “1”; } } |
Теперь, когда тест пройден, пришло время рефакторинга . Я удаляю дублирование из теста:
|
01
02
03
04
05
06
07
08
09
10
11
|
public class WhenFizzingAndBuzzing { @Test public void shouldReplaceWithFizzAndBuzz() {– assertEquals(“1”, “1”, fizzbuzz.get(1));+ assertFizzBuzz(“1”, 1);+ }++ private void assertFizzBuzz(String expected, int n) {+ assertEquals(Integer.toString(n), expected, fizzbuzz.get(n)); } } |
Далее я добавляю тест, чтобы вызвать реальную реализацию:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
public class WhenFizzingAndBuzzing { @Test public void shouldReplaceWithFizzAndBuzz() { assertFizzBuzz(“1”, 1);+ assertFizzBuzz(“2”, 2); } private void assertFizzBuzz(String expected, int n) { package remonsinnema.blog.fizzbuzz; public class FizzBuzz {– public String get(int i) {– return “1”;+ public String get(int n) {+ return Integer.toString(n); } } |
Хорошо, теперь давайте попробуем проверить Fizz :
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
public class WhenFizzingAndBuzzing {public void shouldReplaceWithFizzAndBuzz() {assertFizzBuzz(“1”, 1);assertFizzBuzz(“2”, 2);+ assertFizzBuzz(“Fizz”, 3);}private void assertFizzBuzz(String expected, int n) {package remonsinnema.blog.fizzbuzz;public class FizzBuzz {public String get(int n) {+ if (n == 3) {+ return “Fizz”;+ }return Integer.toString(n);} |
Аналогично для Buzz :
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
public class WhenFizzingAndBuzzing { assertFizzBuzz(“Fizz”, 3);+ assertFizzBuzz(“4”, 4);+ assertFizzBuzz(“Buzz”, 5); } private void assertFizzBuzz(String expected, int n) { public class FizzBuzz { if (n == 3) { return “Fizz”; }+ if (n == 5) {+ return “Buzz”;+ } return Integer.toString(n); } |
Здесь я просто скопировал и вставил оператор if чтобы он работал быстро . Мы не должны останавливаться на достигнутом, конечно, но избавиться от грязных вещей. В данном случае это дублирование.
Во-первых, давайте обновим код, чтобы сделать дублирование более очевидным:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
|
package remonsinnema.blog.fizzbuzz; public class FizzBuzz { public String get(int n) {– if (n == 3) {– return “Fizz”;+ MultipleReplacer replacer = new MultipleReplacer(3, “Fizz”);+ if (n == replacer.getValue()) {+ return replacer.getText(); }– if (n == 5) {– return “Buzz”;+ replacer = new MultipleReplacer(5, “Buzz”);+ if (n == replacer.getValue()) {+ return replacer.getText(); } return Integer.toString(n); } |
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
|
+ package remonsinnema.blog.fizzbuzz;+++ public class MultipleReplacer {++ private final int value;+ private final String text;++ public MultipleReplacer(int value, String text) {+ this.value = value;+ this.text = text;+ }++ public int getValue() {+ return value;+ }++ public String getText() {+ return text;+ }++ } |
Я только что создал новый объект значения для хранения двух значений, которые мне пришлось изменить после копирования / вставки.
Теперь, когда дублирование стало понятнее, его легко удалить:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
package remonsinnema.blog.fizzbuzz;+ import java.util.Arrays;+ import java.util.Collection;+ public class FizzBuzz {+ private final Collection replacers = Arrays.asList(+ new MultipleReplacer(3, “Fizz”), new MultipleReplacer(5, “Buzz”));+ public String get(int n) {– MultipleReplacer replacer = new MultipleReplacer(3, “Fizz”);– if (n == replacer.getValue()) {– return replacer.getText();– }– replacer = new MultipleReplacer(5, “Buzz”);– if (n == replacer.getValue()) {– return replacer.getText();+ for (MultipleReplacer replacer : replacers) {+ if (n == replacer.getValue()) {+ return replacer.getText();+ } } return Integer.toString(n); } |
Я не закончил уборку, однако. Текущий код страдает от зависти к функциям , которую я решаю, перемещая поведение в объект значения:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
package remonsinnema.blog.fizzbuzz; import java.util.Arrays; import java.util.Collection;+ import java.util.Optional; public class FizzBuzz { public String get(int n) { for (MultipleReplacer replacer : replacers) {– if (n == replacer.getValue()) {– return replacer.getText();+ Optional result = replacer.textFor(n);+ if (result.isPresent()) {+ return result.get(); } } return Integer.toString(n); |
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
|
package remonsinnema.blog.fizzbuzz;+ import java.util.Optional;+ public class MultipleReplacer { this.text = text; }– public int getValue() {– return value;– }–– public String getText() {– return text;+ public Optional<String> textFor(int n) {+ if (n == value) {+ return Optional.of(text);+ }+ return Optional.empty(); } } |
Теперь, когда я закончил рефакторинг, я могу продолжить с кратных значений:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
public class WhenFizzingAndBuzzing { assertFizzBuzz(“Fizz”, 3); assertFizzBuzz(“4”, 4); assertFizzBuzz(“Buzz”, 5);+ assertFizzBuzz(“Fizz”, 6); } private void assertFizzBuzz(String expected, int n) { public class MultipleReplacer { } public Optional<String> textFor(int n) {– if (n == value) {+ if (n % value == 0) { return Optional.of(text); } return Optional.empty(); |
Финальный тест для одновременного «Fizz» и «Buzz»:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
public class WhenFizzingAndBuzzing { assertFizzBuzz(“4”, 4); assertFizzBuzz(“Buzz”, 5); assertFizzBuzz(“Fizz”, 6);+ assertFizzBuzz(“7”, 7);+ assertFizzBuzz(“8”, 8);+ assertFizzBuzz(“Fizz”, 9);+ assertFizzBuzz(“Buzz”, 10);+ assertFizzBuzz(“11”, 11);+ assertFizzBuzz(“Fizz”, 12);+ assertFizzBuzz(“13”, 13);+ assertFizzBuzz(“14”, 14);+ assertFizzBuzz(“FizzBuzz”, 15); } private void assertFizzBuzz(String expected, int n) { |
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
|
public class FizzBuzz { public class FizzBuzz { new MultipleReplacer(3, “Fizz”), new MultipleReplacer(5, “Buzz”)); public String get(int n) {+ StringBuilder result = new StringBuilder(); for (MultipleReplacer replacer : replacers) {– Optional<String> result = replacer.textFor(n);– if (result.isPresent()) {– return result.get();+ Optional<String> replacement = replacer.textFor(n);+ if (replacement.isPresent()) {+ result.append(replacement.get()); } }+ if (result.length() > 0) {+ return result.toString();+ } return Integer.toString(n); } } |
Этот код довольно сложный, но здесь на помощь приходят потоки:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
|
public class FizzBuzz { new MultipleReplacer(3, “Fizz”), new MultipleReplacer(5, “Buzz”)); public String get(int n) {– StringBuilder result = new StringBuilder();– for (MultipleReplacer replacer : replacers) {– Optional<String> replacement = replacer.textFor(n);– if (replacement.isPresent()) {– result.append(replacement.get());– }– }– if (result.length() > 0) {– return result.toString();– }– return Integer.toString(n);+ return replacers.stream()+ .map(replacer -> replacer.textFor(n))+ .filter(Optional::isPresent)+ .map(optional -> optional.get())+ .reduce((a, b) -> a + b)+ .orElse(Integer.toString(n)); } } |
Обратите внимание, как исчезают операторы for и if . Вместо того, чтобы объяснять, как что-то нужно сделать, мы говорим, чего хотим достичь.
Мы можем применить тот же трюк, чтобы избавиться от оставшегося оператора if в нашей базе од:
|
01
02
03
04
05
06
07
08
09
10
11
|
public class MultipleReplacer { } public Optional<String> textFor(int n) {– if (n % value == 0) {– return Optional.of(text);– }– return Optional.empty();+ return Optional.of(text)+ .filter(ignored -> n % value == 0); } } |
Код есть на GitHub .
| Ссылка: | FizzBuzz Kata с потоками Java от нашего партнера по JCG Ремона Синнема в блоге по разработке безопасного программного обеспечения . |