forked from twitter/effectivescala
-
Notifications
You must be signed in to change notification settings - Fork 0
/
effectivescala-ru.mo
1314 lines (882 loc) · 108 KB
/
effectivescala-ru.mo
1
2
3
4
5
6
7
8
9
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
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<a href="http://github.com/twitter/effectivescala"><img style="position: absolute; top: 0; left: 0; border: 0;" src="https://s3.amazonaws.com/github/ribbons/forkme_left_green_007200.png" alt="Fork me on GitHub"></a>
<h1 class="header">Effective Scala</h1>
<address>Marius Eriksen, Twitter Inc.<br />[email protected] (<a href="http://twitter.com/marius">@marius</a>)<br /><br />[translated by Eugene Sysmanov(<a href="https://github.com/appigram">@appigram</a>)]</address>
<h2>Table of Contents</h2>
.TOC
<h2>Перевод на другие языки</h2>
<a href="index.html">English</a>
<a href="index-ja.html">日本語</a>
<a href="index-cn.html">简体中文</a>
<a href="index-ko.html">한국어</a>
## Введение
[Scala][Scala] является одним из основных языков программирования, который испоьзуется в Twitter. Большая часть нашей инфрастуктуры написана на Scala и [у нас есть несколько крупных библиотек](http://github.com/twitter/), которые нами поддерживаются.
Scala не только очень эффективный, но и большой язык. Полученный нами опыт, научил нас быть очень осторожным при его использовании в своих приложениях в боевых условиях. Какие есть подводные камни? Какие особенности стоит использвать, а от каких отказаться? Когда мы можем использовать «чисто функциональный стиль», а когда его следует избегать? Другими словами: что мы ежедневно используем, чтобы быть более эффективными используя этот язык? Это руководство пытается передать наш опыт в коротких заметках, представляя их как набор *лучших практик*. Мы используем Scala для создания высококачественных сервисов, которые представляют собой распределенные системы - наше мнение возможно будет предвзято - но большинство советов здесь должны работать без проблем и
при переносе на другие системы. Все эти советы не явяются истиной в последней инстанции, и небольшое отклонение должно быть вполне приемлимым.
Scala предоставляет множество инструментов, которые позволяют кратко описывать свои действия. Если мы меньше будем набирать текста, значит меньше придется читать, а значит исходный код будет прочитан быстрее, поэтому краткость кода повышает его ясность. Однако краткость может быть оказаться и плохим помощником, который может оказать обратный эффект: вслед за правильностью, всегда нужно думать о читателе.
Немного, о *Scala-программе*. Вы не пишете код ни на Java, ни на Haskell, ни на Python; написание Scala-программы отличается от написания на любом из этих языков. Для того чтобы использовать язык эффективно, вы должны описать свои проблемы, в терминах этого языка. Вас никто не принуждает использовать программу, написанную на Java, в Scala, в большинстве случаев она будет уступать оригиналу.
Данный документ не введение в язык Scala, мы предполагаем, что читатель
знаком с языком. Вот некоторые ресурсы для обучения языку Scala:
* [Scala School](http://twitter.github.com/scala_school/)
* [Learning Scala](http://www.scala-lang.org/node/1305)
* [Learning Scala in Small Bites](http://matt.might.net/articles/learning-scala-in-small-bites/)
Данный набор статей - это живой документ, который будет меняться с учетом наших текущих "лучших практик", но основные идеи вряд ли изменятся: писать код, который всегда будет легко читаем; писать универсальный код, но не в ущерб
ясности; пользоваться простыми функциями языка, которые обладают большой
мощью, но избегать эзотерических функций (особенно в системе типов).
Прежде всего, всегда нужно находить компромиссы в том, что вы делаете. Сложность языка требуется в комплексных реализациях, потому что она порождает сложность: в рассуждениях, в семантике, во взаимодействия между особенностями системы, а также в понимании между вашими сотрудниками. Таким образом, трудность является производной от сложности - вы всегда должны убедиться, что ее полезность превышает ее стоимость.
И получайте удовольствие.
## Форматирование
Специфические способы *форматирования кода* - пока они полезны -
не имеют большого значения. По определению, стиль не может быть хорошим или плохим, почти все определяет личное предпочтение. Однако, *последовательное* применение одних и тех же правил форматирования будет почти всегда увеличивать удобочитаемость. Читатель, уже знакомый с данным стилем, не должен разбираться в еще одном наборе местных соглашений или расшифровать еще одну часть языковой грамматики.
Это имеет особое значение для Scala, поскольку у его грамматики высокий уровень вхождения. Один говорящий пример - вызов метода: Методы
могут быть вызваны с помощью "`.`", либо с использованием пробела, либо без круглой скобки для не методов не возвращающих значений или для унарных методов, с круглой скобкой для этих же случаев, и так далее. Кроме того, различные стили вызова методов оставляют двусмысленность в его грамматике! Конечно, последовательное применение заранее определенного набора правил форматирования решит большую часть двусмысленности и для человека и для машины.
Мы придерживаемся [Правила форматирования в языке Scala] (http://docs.scala-lang.org/style/) и дополнительно следующих правил.
### Пробельные символы
При отступе используется 2 пробельных символа. Мы стараемся избегать строк, длиной более 100 символов. Мы используем одну пустую строку между методом, классом и определениями объекта.
### Именование
<dl class="rules">
<dt>Используйте короткие имена для небольших областей видимости</dt>
<dd>Применяйте <code>i</code>,<code>j</code> и <code>k</code> и тому подобные переменные в циклах </dd>
<dt>Используйте длинные имена для больших областей видимости</dt>
<dd>Внешние API должны иметь длинные и понятные имена, которые придают смысл.
<code>Future.collect</code> вместо <code>Future.all</code>.
</dd>
<dt>Используйте стандартные сокращения, и откажитесь от эзотерических</dt>
<dd>
Всем известны <code>ok</code>, <code>err</code> или <code>defn</code>,а вот <code>sfri</code> используется не так часто.
</dd>
<dt>Не используйте одни и теже имена для различных целей</dt>
<dd>Применяйте <code>val</code></dd>
<dt>Избегайте использования <code>`</code> для перегрузки зарезервированных имен.</dt>
<dd>Используйте <code>typ</code> вместо <code>`type</code>`</dd>
<dt>Используйте в имени active для операций с побочными эффектами</dt>
<dd><code>user.activate()</code> вместо <code>user.setActive()</code></dd>
<dt>Используйте описательные имен для методов, которые возвращают значения</dt>
<dd><code>src.isDefined</code> вместо <code>src.defined</code></dd>
<dt>Не используйте у геттеров(getter) префикс <code>get</code></dt>
<dd>В соответствии с предыдущим правилом: лучше применять <code>site.count</code> вместо <code>site.getCount</code></dd>
<dt>Не используйте повторно имена, которые уже есть в пакете или в названии объекта</dt>
<dd>Предпочтительно:
<pre><code> object User {
def get(id: Int): Option[User]
}</code></pre>вместо
<pre><code>object User {
def getUser(id: Int): Option[User]
}</code></pre>Они являются избыточными, так как при использовании: <code>User.getUser</code> дает не больше информации, чем <code>User.get</code>.
</dd>
</dl>
### Импорт
<dl class="rules">
<dt>Располагайте строки импорта в алфавитном порядке</dt>
<dd>Так их проще определить визуально, и так проще для автоматизации.</dd>
<dt>Используйте фигурные скобки при импортировании нескольких имен из пакета</dt>
<dd><code>import com.twitter.concurrent.{Broker, Offer}</code></dd>
<dt>Используйте символ подчеркивания, когда импортируйте более 6 имен</dt>
<dd>например: <code>import com.twitter.concurrent._</code>
<br />Не применяйте этот знак без оглядки, некоторые пакеты экспортируют слишком много имен</dd>
<dt>Когда используете коллекции, уточняйте имена при импортировании <code>scala.collection.immutable</code> и/или <code>scala.collection.mutable</code></dt>
<dd>Изменяемые и неизменяемые коллекции имеют двойные имена. Уточнение имен сделает очевидным для читателя какой вариант используется (например "<code>immutable.Map</code>")</dd>
<dt>Не используйте относительный импорт из других пакетов</dt>
<dd>Избегайте <pre><code>import com.twitter
import concurrent</code></pre> в пользу более одназначного <pre><code>import com.twitter.concurrent</code></pre></dd>
<dt>Располагайте строки импорта вверху файла</dt>
<dd>Читатель может обратится ко всем строкам импорта в одном месте</dd>
</dl>
### Фигурные скобки
Фигурные скобки используются для создания сложных выражений (они служат другим целям в "module language"), где значение соответствующего выражения является последним выражением в списке. Старайтесь не использовать скобки для простых выражений; пишите
def square(х: Int) = х*х
.LP, вместо
def square(х: Int) = {
х * х
}
.LP, хотя это может быть привлекательным, чтобы отличить тело метода синтаксически. Первый вариант имеет меньший беспорядок и его легче читать. <em>Избегайте лишних синтаксических консрукций</em>, если это не уточняется дополнительно.
### Сравнение с образцом
Используйте сравнение с образцом в определении функций, когда это необходимо;
Вместо
list map { item =>
item match {
case Some(x) => x
case None => default
}
}
.LP лучше написать так
list map {
case Some(x) => x
case None => default
}
.LP ясно, что элементы списка в настоящее время отображаются более ясно — дополнительно уточнять ничего не нужно.
### Комментарии
Используйте [ScalaDoc](https://wiki.scala-lang.org/display/SW/Scaladoc), чтобы предоставлять документацию по API. Используйте следующий стиль:
/**
* ServiceBuilder builds services
* ...
*/
.LP <em>вместо</em>стандартного стиля ScalaDoc:
/** ServiceBuilder builds services
* ...
*/
Не прибегайте к ASCII искусству или другим визуальным украшениям. Документируйте API, но не добавляйте ненужных комментариев. Если вы добавляете комментарии, чтобы объяснить поведение вашего кода, сначала спросите себя, может ли код быть переписан так, чтобы стало очевидным, что он делает. Лучше предпочесть "Очевидно, это работает" вместо "Это работает, очевидно" (цитата Энтони Хоара).
(прим. переводчика: "Есть два метода создания программного обеспечения. Один из них — сделать программу настолько простой, что, очевидно, в ней нет недостатков. И другой, сделать приложение настолько сложным, что в нем невидно явных недостатков." - [Энтони Хоар](http://ru.wikipedia.org/wiki/Хоар,_Чарльз_Энтони_Ричард) выдержка из лекции, [Премия Тьюринга](http://amturing.acm.org/award_winners/hoare_4622167.cfm))
## Типы и обобщенные типы
Основной целью системы типов является выявление ошибок программирования. Система типов эффективно обеспечивает определенную форму статической проверки, что позволяет нам получать определенный набор неизменных параметров о нашем коде, который компилятор может проверить. Система типов конечно обеспечивает и другие преимущества, но проверка ошибок является ее основной целью.
Использование системы типов должно отражать эту цель, но мы должны не забывать и о читателе: разумное использование типов может служить повышением ясности, слишком все усложнять, значит запутывать остальных.
Мощная система типов в Scala является результатом общих усилий различных академических опытов и разработок (например, [Scala программирование на уровне системы типов] (http://apocalisp.wordpress.com/2010/06/08/type-level-programming-in-scala/)).
В то время как это увлекательная академическая статья, эти методы редко находят полезное применение в реальном коде приложений. Их можно избегать.
### Возвращаемый тип аннотаций
В то время как Scala позволяет опускать их, такие аннотации обеспечивают хорошую документацию: это особенно важно для публичных методов. Там где возвращаемый тип метода очевиден, их можно опустить.
Это особенно важно при создании экземпляров объектов с миксинами(Mixins), так как компилятор Scala создает тип синглтон(singleton) для них. Например, `make` в примере:
trait Service
def make() = new Service {
def getId = 123
}
.LP <em>не</em> имеет возвращаемого типа <code>Service</code>; компилятор создает <code>Object with Service{def getId: Int}</code>. Вместо того, чтобы использовать явную аннотацию:
def make(): Service = new Service{}
В настоящее время автор свободен смешивать множество трейтов(traits) без изменения общедоступного типа `make`, делая возможность легче управлять обратной совместимостью.
### Расхождение
Расхождение возникает, когда обобщеные типы объединены с выделением подтипов. Они определяют, как выделение подтипов типа *contained* относится к выделению подтипов типа *container*. Потому что Scala имеется определенное соглашение по комментированию, и авторы общих библиотек - особенно коллекций -
должны активно писать комментарии. Такие комментарии важны для
удобства работы с общим кодом, но неверные комментарии могут быть опасны.
Инварианты - усовершенствованный, но необходимый компонент системы типов Scala,и должны использоваться широко (и правильно), поскольку это помогает приложению в выделении подтипов.
*Коллекции Immutable должны быть ковариантны*. Методы, которые получают
contained тип должны быть "понижены"("downgrade") до соответствующей коллекции:
trait Collection[+T] {
def add[U >: T](other: U): Collection[U]
}
*Mutable коллекции должны быть инвариантны*. Ковариация обычно бессмыслена с изменяемыми(mutable) коллекциями. Рассмотрим
trait HashSet[+T] {
def add[U >: T](item: U)
}
.LP и следующую иерархию типов:
trait Mammal
trait Dog extends Mammal
trait Cat extends Mammal
.LP Допустим у нас сейчас есть хеш-коллекция из объектов Собака
val dogs: HashSet[Dog]
.LP создаем хеш-коллекцию Млекопитающих и добавляем в колллекцию объект Кошка
val mammals: HashSet[Mammal] = dogs
mammals.add(new Cat{})
.LP Теперь эта хеш-коллекция не является коллекцией объектов Собака!
(прим. переводчика: Подробнее о [ковариантности и контрвариантности](http://chabster.blogspot.com/2008/01/type-system-covariance-and.html))
<!--
* when to use abstract type members?
* show contravariance trick?
-->
### Псевдонимы типов
Используйте псевдонимы типов, когда они позволяют удобно именовать или разъяснять цели, но не искажают типы, которые и так очевидны.
() => Int
.LP данная запись более понятна чем
type IntMaker = () => Int
IntMaker
.LP так как это короче и используется общий тип, однако
class ConcurrentPool[K, V] {
type Queue = ConcurrentLinkedQueue[V]
type Map = ConcurrentHashMap[K, Queue]
...
}
.LP более полезен, так как передает цель и улучшает краткость.
Не используйте разделение на подклассы, когда псевдоним делает тоже самое
trait SocketFactory extends (SocketAddress => Socket)
.LP <code>SocketFactory</code> <em>это</em> функция которая создает <code>Socket</code>. Использование псевдонима
type SocketFactory = SocketAddress => Socket
.LP более правильно. Теперь мы можем обеспечить функциональные идентификаторы для значений типа<code>SocketFactory</code>, а также использовать композицию функций:
val addrToInet: SocketAddress => Long
val inetToSocket: Long => Socket
val factory: SocketFactory = addrToInet andThen inetToSocket
Псевдонимы типов связаны с именами, которые стоят выше в иерархии, при помощи объектов пакета:
package com.twitter
package object net {
type SocketFactory = (SocketAddress) => Socket
}
Обратите внимание на то, что псевдонимы, это не новые типы - они эквивалентны
синтаксической замене типа новым именем.
### Неявные преобразования
Неявные преобразования являются мощной возможностью системы типов, но они должны использоваться там, где необходимы. Они усложняют правила преобразования трудоемкими -- если по простому, то лексическим сравнением -- чтобы понять, что на самом деле происходит. Обычно неявные преобразования используются в следующих ситуациях:
* Расширение или добавление коллекций в стиле Scala
* Адаптация или расширение объекта (шаблон "pimp my library")
* Для *повышения безопасности типов*, предоставляя ограниченный набор данных
* Чтобы предоставить данные типа (typeclassing)
* Для `Манифестов`
Если действительно хотите использовать неявные преобразования, прежде всего спросите себя, есть ли способ достигнуть той же цели без их помощи.
Не используйте неявные преобразования, чтобы сделать автоматическое преобразование между похожими типами данных (например, преобразование списка в поток); это лучше сделать явно, потому что у типов есть различная семантика, и читатель должен остерегаться подобных реализаций.
## Коллекции
У Scala есть универсальная, богатая, мощная, и прекрасно составленная библиотека коллекций; коллекции - высокоуровневые реализации и они представляют собой большой набор различный операций. Множество действий с коллекциями и преобразования над ними могут быть выражены кратко и четко, но небрежное применение функций может привести к противоположному результату. Каждый Scala-программист должен прочитать [Сollections design document](http://www.scala-lang.org/docu/files/collections-api/collections.html);
он даст большее понимание при работе с библиотекой коллекций в Scala.
Всегда используйте самую простую коллекцию, которая соответствует вашим потребностям потребностям.
### Иерархия
Библиотека коллекций очень большая: в дополнение к сложной
иерархии - корнем которой является `Traversable[T]` - существуют
`immutable` и `mutable` варианты для большинства коллекций. Несмотря на сложность, на следующей диаграмме содержатся важные
различия между `immutable` и `mutable` иерархиями
<img src="coll.png" style="margin-left: 3em;" />
.cmd
pic2graph -format png >coll.png <<EOF
boxwid=1.0
.ft I
.ps +9
Iterable: [
Box: box wid 1.5*boxwid
"\s+2Iterable[T]\s-2" at Box
]
Seq: box "Seq[T]" with .n at Iterable.s + (-1.5, -0.5)
Set: box "Set[T]" with .n at Iterable.s + (0, -0.5)
Map: box "Map[T]" with .n at Iterable.s + (1.5, -0.5)
arrow from Iterable.s to Seq.ne
arrow from Iterable.s to Set.n
arrow from Iterable.s to Map.nw
EOF
.endcmd
.LP <code>Iterable[T]</code> - это любая коллекция, элементы которой могут быть проитерированы, она имеет метод <code>iterator</code>(а также метод <code>foreach</code>).<code>Seq[T]</code>- это коллекция, элементы которой <em>отсортированы</em>,<code>Set[T]</code>- является аналогом математического множества (неупорядоченная коллекция уникальных элементов), и <code>Map[T]</code> - который представляет собой неотсортированный ассоциативный массив.
### Применение
* Предпочитительнее использовать immutable коллекции.* Они применимы в большинстве случаев, и делают программу проще и прозрачнее,а также потокобезопасной.
* Используйте `mutable` пространство имен явно.* Не импортируйте
`scala.collection.mutable._`, а ссылку на `set`, лучше сделать так
import scala.collections.mutable
val set = mutable.Set()
.LP так становится ясно, что используется mutable вариант
* Используйте стандартный конструктор для коллекций.* Всякий раз, когда вам нужна упорядоченная последовательность (и не обязательно связанный список), используйте конструктор `Seq()`, или ему подобный вариант:
val seq = Seq(1, 2, 3)
val set = Set(1, 2, 3)
val map = Map(1 -> "one", 2 -> "two", 3 -> "three")
.LP Этот стиль отделяет семантику коллекции от ее реализации, позволяя библиотеке коллекций использовать наиболее подходящий тип: если вам нужен <code>Map</code>, не обязательно использовать <a href="http://ru.wikipedia.org/wiki/Красно-чёрное_дерево">Красно-черное дерево(Red-Black Tree)</a>. Кроме того, стандартные конструкторы будут часто использовать специализированные представления: например, <code>Map()</code> будет использовать объект с 3 полями для объектов с 3 ключами(<a href="http://www.scala-lang.org/api/current/scala/collection/immutable/Map$$Map3.html"><code>Map3</code></a>).
В заключение, к сказанному выше: в ваших собственных методах и конструкторах *старайтесь использовать самую универсальную коллекцию*. Обычно это сводится к одной из приведенных: `Iterable`, `Seq`, `Set`, или `Map`. Если ваш метод нуждается в последовательности, используйте `Seq[T]`, а не `List[T]`.
<!--
something about buffers for construction?
anything about streams?
-->
### Стиль
Функциональное программирование призывает к цепочечным преобразованиям
immutable коллекций для получения желаемого результата. Это часто
приводит к очень коротким решениям, но также может ввести в заблуждение
читателя - часто трудно понять намерения автора, или отслеживать все промежуточные результаты, которые подразумеваются. Например,
предположим, что мы хотим подсчитать голоса за разные языки программирования
из определенного набора (язык, число голосов); показывая их, в порядке убывания числа голосов, мы могли бы написать:
val votes = Seq(("scala", 1), ("java", 4), ("scala", 10), ("scala", 1), ("python", 10))
val orderedVotes = votes
.groupBy(_._1)
.map { case (which, counts) =>
(which, counts.foldLeft(0)(_ + _._2))
}.toSeq
.sortBy(_._2)
.reverse
.LP это кратко и правильно, но почти любому читателю нужно время, чтобы восстановить в голове первоначальные намерения автора. Стратегия, которая призвана уточнить решение <em>использование промежуточных результатов и параметров</em>:
val votesByLang = votes groupBy { case (lang, _) => lang }
val sumByLang = votesByLang map { case (lang, counts) =>
val countsOnly = counts map { case (_, count) => count }
(lang, countsOnly.sum)
}
val orderedVotes = sumByLang.toSeq
.sortBy { case (_, count) => count }
.reverse
.LP код является почти столь же кратким, но гораздо более четко описывает происходящие превращения (благодаря именованным промежуточным значениям), и структуры данных с которыми работает программа (именованные параметры). Если вы беспокоитесь о засорении пространства имен, применяя этот стиль, используйте группировку выражений с помощью <code>{}</code>:
val orderedVotes = {
val votesByLang = ...
...
}
### Производительность
Высокоуровневые библиотеки коллекций (как обычно и с высокоуровневыми конструкциями), делают определение производительности более трудоемким:
чем дальше вы отклоняетесь от указания конкретных команд компьютеру - другими словами, императивного стиля - тем тяжелее предсказать точное значение производительности участка кода. Обосновать правильность, однако, обычно проще; также повышается удобство чтения кода. Со Scala ситуация осложнена средой исполнения Java; Scala скрывает операции упаковки/распаковки от нас, но они могут серьезно влиять на производительность или оказывать противоположное действие.
Прежде, чем сфокусироваться на низкоуровневых деталях, удостоверьтесь, что используете коллекцию, подходящую для данного случая. Удостоверьтесь, что ваша структура данных не имеет неожиданной асимптотической сложности. Возникающие при работе сложности для разных Scala коллекций описаны [здесь](http://www.scala-lang.org/docu/files/collections-api/collections_40.html).
Первое правило оптимизации производительности состоит в том, чтобы понять *почему* ваше приложение медленно работает. Не стоит действовать без оглядки; профилируйте^ [Yourkit](http://yourkit.com) - неплохой профилировщик] приложение перед тем как действовать дальше. Фокусируйтесь сначала на интенсивно используемых циклах и больших структурах данных. Чрезмерные усилия на оптимизацию обычно тратятся впустую. Помните принцип Кнута: "Преждевременная оптимизация - корень все зол."
Очень часто целесообразно использовать низкоуровневые коллекции в ситуациях где требуется лучшая производительность или иметь задел эффективности на будущее. Используйте массивы вместо списков для больших последовательностей (неизменяемая `Vector` коллекция обеспечивает похожий интерфейс для массивов); и используйте буферы вместо конструирования последовательности, когда стоят вопросы производительности.
### Java Коллекции
Используйте `scala.collection.JavaConverters` для взаимодействия с коллекциями Java. Эта коллекция неявно добавляет методы преобразования `asJava` и `asScala`. Их использование гарантирует, что такие преобразования являются явными, помогая читателю:
import scala.collection.JavaConverters._
val list: java.util.List[Int] = Seq(1,2,3,4).asJava
val buffer: scala.collection.mutable.Buffer[Int] = list.asScala
## Параллелизм
Современные сервисы обладают высоким уровнем параллелизма - серверы выполняют 10-100 тысяч одновременных операций - и их обработка подразумевают сложность, которая является центральной темой в надежных программных системах.
*Потоки* являются средством выражения параллелизма: они дают вам независимые контексты выполнения с общей разделяемой памятью, которая управляется операционной системой. Тем не менее, создание потоков является затратной операцией в Java и этим ресурсом необходимо управлять, как правило, с использованием пулов. Это создает дополнительные сложности для программиста, а также обладает высокой степенью связности: трудно отделить логику приложения от используемых им основных ресурсов.
Эта сложность особенно заметна при создании сервисов, которые
имеют высокую степень параллелизма: каждый приходящий результат запроса в множество всех запросов на каждый уровень системы. В таких системах, пулы потоков должны быть организованы таким образом, чтобы они быди сбалансированы
в зависимости от количества запросов на каждом уровне: беспорядок в одном пуле потоков негативно влияет на другие.
Надежная система должна также не упускать из виду тайм-ауты и отказы, оба этих элемента требуют введения дополнительного «контроля» потоков, тем самым усложняя проблему еще больше. Заметим, что если бы потоки были более дешевыми, то эти проблемы стали бы меньше: необходимость в пулах, тайм-аутах потоков можно было бы отбросить, и никаких дополнительных ресурсов
для управления не потребовалось бы.
Таким образом, управление ресурсами ставит под угрозу модульность.
### Futures
Используйте futures(актор с возможностью блокировки создающего его потока, если создающий поток запросил результат вычисления - прим. переводчика) для управления параллелизмом. Они позволяют отделить параллельные операции от управления ресурсами: например, [Finagle][Finagle] объединяет параллельные операции в несколько потоков эффективным
образом. Scala имеет легковесный синтаксис замыканий, поэтому futures вводят немного синтаксического сахара; и они становятся все более популярны среди программистов.
Futures позволяют программисту выразить параллельные вычисления в декларативном стиле, скомпоновать, и управлять источником ошибки. Эти качества убедили нас в том, что они особенно хорошо подходят для использования в функциональных языках программирования, где подобный стиль поощряется.
*Изменяйте futures вместо создания собственных.* Futures позволяют поймать ошибку, определить сигнал отказа, и позволяет программисту не думать о релизации модели памяти в Java. Осторожный программист может написать следующее решение для RPC последовательности из 10 элементов, а затем напечатать результаты:
val p = new Promise[List[Result]]
var results: List[Result] = Nil
def collect() {
doRpc() onSuccess { result =>
results = result :: results
if (results.length < 10)
collect()
else
p.setValue(results)
} onFailure { t =>
p.setException(t)
}
}
collect()
p onSuccess { results =>
printf("Got results %s\n", results.mkString(", "))
}
Программист должен убедиться, что RPC ошибки будут распространяться дальше,
объединяя код и контроль потока выполнения; хуже того, этот код неверен! Без объявления переменной `results`, мы не можем гарантировать, что `results` содержит предыдущее значение на каждой итерации. Модель памяти Java не так проста как кажется, но, к счастью, мы можем избежать всех этих ошибок с помощью
декларативного стиля:
def collect(results: List[Result] = Nil): Future[List[Result]] =
doRpc() flatMap { result =>
if (results.length < 9)
collect(result :: results)
else
result :: results
}
collect() onSuccess { results =>
printf("Got results %s\n", results.mkString(", "))
}
Мы используем `flatMap` в последовательности операций и сохраняем результат в список, пока идут вычисления. Это общая идея функциональных языков программирования
реализована в Futures. Для этого требуется меньше заготовок, возникает меньше ошибок, а также читается лучше.
*Используйте Futures комбинаторы*. `Future.select`, `Future.join`, и `Future.collect` реализуют общие шаблоны при работе над несколькими Futures, которые должны быть объединены.
### Коллекции
С параллельными коллекциями связано множество мнений,
тонкостей, догм, страха, неуверенности и сомнения. В большинстве практических ситуаций, они не являются проблемой: Всегда начинайте с самой простой, самой невзрачной, и
стандартной коллекции, которая послужит поставленной цели. Не используйте параллельную коллекцию до того, как вы будете *знать*, что синхронизированный вариант коллекции не работает: JVM имеет современные механизмы, чтобы сделать синхронизацию дешевой операцией, так что их эффективность может вас удивить.
Если необходимо использовать immutable коллекцию, используйте - она совершенно прозрачна, поэтому рассуждать о них в контексте параллельных вычислений очень просто. Изменения в immutable коллекциях, как правило, выполняются путем обновления ссылки на текущее значение (в `var` ячейке или
`AtomicReference`). Необходимо соблюдать осторожность, чтобы верно это применять: атомы должны быть повторно объявлены, и `переменные` должны быть объявлены на лету в порядке их объявления в других потоках.
Mutable параллельные коллекции имеют сложную семантику, и используют тонкости модели памяти в Java, поэтому убедитесь, что вы понимаете последствия - особенно при распространении обновлений - прежде чем начинать использовать их. Синхронные коллекции тоже неплохой вариант: операции, такие как `getOrElseUpdate` не могут быть правильно реализованы для параллельных коллекций, и создание сложных коллекций особенно подвержено ошибкам.
<!--
use the stupid collections first, get fancy only when justified.
serialized? synchronized?
blah blah.
Async*?
-->
## Управляющие структуры
Программы в функциональном стиле, как правило, требуют меньше традиционных управляющих структур, да и читать код лучше, когда он написан в декларативном стиле. Это обычно означает, разделение вашей логики на несколько небольших методов или функций, и склеивание их вместе с `match` выражениями. Функциональные программы также имеют тенденцию быть более
ориентированными на выражения: ветви условных выражений для значений того же типа, `for (..) yield` вычисление дополнений, и рекурсия являются обычным делом.
### Рекурсия
*Формулировка проблемы в терминах рекурсии обычно упрощает ее*, и если применяется оптимизация хвостовой рекурсии (которая может быть проверена с помощью аннотации `@tailrec`), компилятор преобразует код в обычный цикл.
Рассмотрим довольно стандартную императивную реализацию кучи <span class = "algo">fix-down</span>:
def fixDown(heap: Array[T], m: Int, n: Int): Unit = {
var k: Int = m
while (n >= 2*k) {
var j = 2*k
if (j < n && heap(j) < heap(j + 1))
j += 1
if (heap(k) >= heap(j))
return
else {
swap(heap, k, j)
k = j
}
}
}
Каждый раз, при входе в цикл, мы работаем с состоянием предыдущей итерации. Значением каждой переменной является результат вычисления функции определенной ветви выполнения, и значение возвращается в середине цикла, если был найден верный результат вычисления (внимательный читатель найдет похожие
аргументы в Дейкстры ["О вреде оператора Go To"](http://www.vspu.ac.ru/~chul/dijkstra/goto/goto.htm)).
Рассмотрим реализацию (хвостовой) рекурсии^[Из [Finagle's heap balancer](https://github.com/twitter/finagle/blob/master/finagle-core/src/main/scala/com/twitter/finagle/loadbalancer/Heap.scala#L41)]:
@tailrec
final def fixDown(heap: Array[T], i: Int, j: Int) {
if (j < i*2) return
val m = if (j == i*2 || heap(2*i) < heap(2*i+1)) 2*i else 2*i + 1
if (heap(m) < heap(i)) {
swap(heap, i, m)
fixDown(heap, m, j)
}
}
.LP здесь каждая итерация начинается с <em>чистого листа</em>, и нет никаких ссылочных полей: инвариантов достаточно. Об этом гораздо легче рассуждать, и этот код проще для чтения. Не существует потери производительности: поскольку метод является хвостовой рекурсией, компилятор переводит его в обычный цикл.
<!--
elaborate..
-->
### Возврат значений
Это не означает, что императивные структуры бесполезны.
Во многих случаях они хорошо подходят для прекращения вычислений вместо условных переходов для всех возможных вариантов окончания вычислений: ведь в приведенном выше `fixDown`, `return` используется для досрочного прекращения, если мы в конце кучи.
Возврат значений может быть использован, чтобы сократить количество ветвлений и установить инварианты. Он помогает читателю за счет уменьшения вложенности и делает легче рассуждения о правильности последующего кода (доступ к элементу массива не может происходить за границей массива). Это особенно полезно в "guard" выражениях:
def compare(a: AnyRef, b: AnyRef): Int = {
if (a eq b)
return 0
val d = System.identityHashCode(a) compare System.identityHashCode(b)
if (d != 0)
return d
// slow path..
}
Используйте `return` для уточнения и улучшения читаемости, но не так, как в императивных языках; избегайте его использования для возврата результатов вычислений. Вместо
def suffix(i: Int) = {
if (i == 1) return "st"
else if (i == 2) return "nd"
else if (i == 3) return "rd"
else return "th"
}
.LP лучше использовать:
def suffix(i: Int) =
if (i == 1) "st"
else if (i == 2) "nd"
else if (i == 3) "rd"
else "th"
.LP но использование выражения <code>match</code> подходит лучше:
def suffix(i: Int) = i match {
case 1 => "st"
case 2 => "nd"
case 3 => "rd"
case _ => "th"
}
Обратите внимание, что использование `returns` тоже имеет свою цену: при использовании внутри замыкания,
seq foreach { elem =>
if (elem.isLast)
return
// process...
}
.LP в байт-коде это реализовано в виде пары исключений catching/throwing, использование которых в реальном коде, влияет на производительность.
### Циклы `for` и генераторы
`for` обеспечивает краткий и естественный способ для циклической обработки данных и их накопления. Это особенно полезно, когда обрабатывается много последовательностей.
Синтаксис `for` противоречит основному механизму выделения и управления замыканиями. Это может привести к непредвиденным расходам и двусмысленности, например
for (item <- container) {
if (item != 2) return
}
. LP может привести к ошибке выполнения, если произойдет задержка при вычислении container, что делает <code>return</code> не локальным!
По этим причинам, обычно предпочтительнее, вызвать `foreach`,
`flatMap`, `map` и `filter` напрямую - но стоит использовать `for` для того, чтобы прояснить вычисления.
### `require` и `assert`
`require` и `assert`, оба оператора описаны в документации. Оба полезны для ситуаций, в которых система типов не может определить нужный вариант типа. `assert`, используется для *инвариантов*, которые предполагаются в коде (или
внутреннем или внешнем), например
val stream = getClass.getResourceAsStream("someclassdata")
assert(stream != null)
В то время как `require` используется для представления API контрактов:
def fib(n: Int) = {
require(n > 0)
...
}
## Функциональное программирование
*Программирование ориентированное на значение* дает много преимуществ, особенно когда используется в сочетании с конструкциями функционального программирования. Этот стиль
делает ставку на преобразовании значений вместо изменения состояния, возвращая код, который более прозрачен, обеспечивая больше инвариантов, а значит и легче в понимании. Case классы, сопоставление с образцом, несвязность, вывод типов, и легковесное создание замыканий и методов образуют синтаксис, который является предметом обсуждений.
### Case классы как алгебраический тип данных
Case классы представляют собой АТД(алгебраический тип данных): они полезны для моделирования большого числа
структур данных и обеспечивают более короткий код с мощными
инвариантами, особенно при использовании сопоставления с образцом. Сопоставление с образцом реализует исчерпывающий анализ, обеспечивая равномерные статические гарантии.
При использовании следующего примера с использованием АТД и Case классов:
sealed trait Tree[T]
case class Node[T](left: Tree[T], right: Tree[T]) extends Tree[T]
case class Leaf[T](value: T) extends Tree[T]
.LP тип <code>Tree[T]</code>имеет два конструктора: <code>Node</code> и <code>Leaf</code>. Объявление типа <code>sealed</code> позволяет компилятору сделать исчерпывающий анализ, поскольку конструкторы не могут быть добавлены в другом исходном файле.
Вместе с использованием сопоставления с образцом, мы получаем краткие и "очевидно правильные" результаты моделирования в коде:
def findMin[T <: Ordered[T]](tree: Tree[T]) = tree match {
case Node(left, right) => Seq(findMin(left), findMin(right)).min
case Leaf(value) => value
}
В то время как рекурсивные структуры, такие как деревья представляют собой классические реализации АТД, областей их полезного применения намного больше. Несвязные объединения, в частности, легко моделируется с помощью абстрактных типов данных; они часто встречаются в различных состояниях машины.
### Опция
Тип `Option` - это контейнер, который либо пуст (`None`), либо полон (`Some(value)`). Он обеспечивает безопасную альтернативу использованию `null`, и должен быть использован вместо null всякий раз, когда это возможно. Эти типы являются коллекциями (содержащими не более одного элемента) и они используют методы коллекций - чаще используйте их!
Пишите
var username: Option[String] = None
...
username = Some("foobar")
.LP вместо
var username: String = null
...
username = "foobar"
.LP первый вариант безопаснее: тип <code>Option</code> статически проверяет что <code>Username</code> должен быть заранее проверен на пустоту.
Применение в условных конструкциях `Option` значений должно быть сделано с помощью `foreach`, вместо
if (opt.isDefined)
operate(opt.get)
.LP пишите
opt foreach { value =>
operate(value)}
Стиль может показаться странным, но обеспечивает большую безопасность (мы не вызываем исключительно `get`) и краткость. Если есть два варианта выполнения, то при использовании сопоставления с образцом получим:
opt match {
case Some(value) => operate(value)
case None => defaultAction()
}
.LP но если все варианты ложные, то для значений по умолчанию, используйте <code>getOrElse</code>
operate(opt getOrElse defaultValue)
Не злоупотребляйте `Option`: если имеется значение по
умолчанию - [*Null Object*](http://en.wikipedia.org/wiki/Null_Object_pattern) - используйте его.
`Option` также предоставляет удобный конструктор для упаковки nullable значения:
Option(getClass.getResourceAsStream("foo"))
.LP выражение является <code>Option[InputStream]</code> который может принимать значение <code>None</code>, в этом случае <code>getResourceAsStream</code> должен возвратить <code>null</code>.
### Сопоставление с образцом
Сопоставление с образцом (`x match { ...`) широко распространено в хорошо написанном Scala коде: оно осуществляет условное выполнение, разбор конструкций, и все это в одной конструкции. При его использовании также повышается четкость и безопасность.
Используйте сопоставление с образцом для реализации переключения типов:
obj match {
case str: String => ...
case addr: SocketAddress => ...
Сопоставление с образцом лучше всего работает в сочетании с разбором конструкций (например, работе с Case классами), вместо
animal match {
case dog: Dog => "dog (%s)".format(dog.breed)
case _ => animal.species
}
.LP лучше написать
animal match {
case Dog(breed) => "dog (%s)".format(breed)
case other => other.species
}
Используйте [custom extractors](http://www.scala-lang.org/node/112), но только с дополнительным конструктором (`apply`), в противном случае их использования может быть неуместным.
Не используйте сопоставление с образцом для вычисления условий, когда имеет смысл использовать стандартные значения. Библиотеки коллекций обычно предоставляют методы, которые возвращают `Option`; избегайте такого кода
val x = list match {
case head :: _ => head
case Nil => default
}
.LP потому что
val x = list.headOption getOrElse default
.LP так короче и больше соответствует цели
### Частичные функции
Scala предоставляет синтаксический сахар для определения `PartialFunction`:
val pf: PartialFunction[Int, String] = {
case i if i%2 == 0 => "even"
}
.LP они могут использоваться вместе с <code>orElse</code>
val tf: (Int => String) = pf orElse { case _ => "odd"}
tf(1) == "odd"
tf(2) == "even"
Частичные функции исопльзуются во многих ситуациях и эффективно
кодируются с помощью `PartialFunction`, например, в качестве аргументов
методов
trait Publisher[T] {
def subscribe(f: PartialFunction[T, Unit])
}
val publisher: Publisher[Int] = ..
publisher.subscribe {
case i if isPrime(i) => println("found prime", i)
case i if i%2 == 0 => count += 2
/* ignore the rest */
}
.LP или в ситуациях, которые могли бы способствовать возврату <code>Option</code>
// Attempt to classify the the throwable for logging.
type Classifier = Throwable => Option[java.util.logging.Level]
.LP все это может быть лучше выражено с помощью <code>PartialFunction</code>
type Classifier = PartialFunction[Throwable, java.util.Logging.Level]
.LP так как это более компактно:
val classifier1: Classifier
val classifier2: Classifier
val classifier = classifier1 orElse classifier2 orElse { _ => java.util.Logging.Level.FINEST }
### Разрушение связей (Destructuring bindings)
Разрушение связей тесно связано с сопоставлением с образцом; используется тот же механизм, но применяется, когда есть только один вариант (чтобы не сгенерировать исключение). Разрушение связей особенно полезно для кортежей и Case классов.
val tuple = ('a', 1)
val (char, digit) = tuple
val tweet = Tweet("just tweeting", Time.now)
val Tweet(text, timestamp) = tweet
### Ленивые вычисления
Поля в Scala вычисляются *при необходимости*, если `val` имеет префикс
`lazy`. Поэтому поля и методы в Scala подобны (поля как бы `private[this]`):
lazy val field = computation()
.LP это сокращение для
var _theField = None
def field = if (_theField.isDefined) _theField.get else {
_theField = Some(computation())
_theField.get
}
.LP где, результаты вычисляются и запоминаются. Используйте ленивые поля для этих случаев, но избегайте использования ленивых вычислений, когда ленивые вычисления требуются по смыслу. В этих случаях лучше производить вычисления явно, поскольку можно точно произвести оценку, и побочные эффекты могут контролироваться более точно.
Ленивые поля являются потокобезопасными.
### Передача по имени
Параметры методов могут быть переданы по имени, то есть параметр принимает не значение, а *вычисление*, которое может повторяться. Это
функция должна применяться с осторожностью; вызывающая функция может ожидать передачу по значению, но ответ ее удивит. Способ применения этой особенности - это построение обычных синтаксических DSL - например, могут быть сделаны новые конструкции управления, чтобы выглядеть так же, как и родные особенности языка.
Используйте передачу по имени управляющих конструкций, когда для вызывающей функции очевидно, что передается в этот "блок", вместо результата каких-то неожиданных вычислений. Записывайте передачу по имени в крайней позиции последнего списка аргументов. При использовании передачи по имени, убедитесь, что метод назван очевидным образом для вызывающей функуции, которая передает аргумент.
Если вы хотите, чтобы значения вычислялись несколько раз, и, особенно, когда вычисление имеет побочные эффекты, используйте явные функции:
class SSLConnector(mkEngine: () => SSLEngine)
.LP ваше намерение остается очевидным и для вызывающей функции это не будет сюрпризом
### `flatMap`
`flatMap` - сочетает в себе `map` и `flatten` - заслуживающие особого внимания, потому что они обладают небольшой мощью, но очень полезны. Подобно `map`, она доступна в нетрадиционных коллекциях, таких как `Future` и `Option`. Поведение функции
раскрывается при описании; например `Container[]`
flatMap[B](f: A => Container[B]): Container[B]
.LP <code>flatMap</code> вызывает функцию <code>f</code> для элемента(ов) из коллекции создавая <em>новую</em> коллекцию, из всех тех, которые дают нужный результат. Например, чтобы получить все перестановки двух символьных строк, у которых нет общих символов, напишем дважды:
val chars = 'a' to 'z'
val perms = chars flatMap { a =>
chars flatMap { b =>
if (a != b) Seq("%c%c".format(a, b))
else Seq()
}
}
.LP что эквивалентено, но более кратко и понятно (что само по себе —грубый— синтаксический сахар для написанного выше), этому коду:
val perms = for {
a <- chars
b <- chars
if a != b
} yield "%c%c".format(a, b)
`flatMap` часто полезна при работе с `Option` - она позволяет
свернуть цепочки вызовов до одного,
val host: Option[String] = ..
val port: Option[Int] = ..
val addr: Option[InetSocketAddress] =
host flatMap { h =>
port map { p =>
new InetSocketAddress(h, p)
}
}
.LP данный пример также кратко можно написать с использованием <code>for</code>
val addr: Option[InetSocketAddress] = for {
h <- host
p <- port
} yield new InetSocketAddress(h, p)
Использование `flatMap` в `Future` обсуждается в
<a href="#Twitter's%20standard%20libraries-Futures">разделе futures</a>.
## Объектно-ориентированное программирование
Большая часть возможностей Scala обеспечивается благодаря объектной системе. Scala является *чистым* языком в этом смысле, потому что *все элементы* являются объектами, нет различия между примитивными типами и составными. Scala также имеет примеси (Mixins), позволяющие более четкое построение модулей, которые можно гибко собрать на этапе компиляции со всеми преимуществами статической проверки типов.
Основная идея системы примесей в том, чтобы избежать необходимости в традиционном построение зависимостей. Кульминацией этого "компонентного стиля" программирования является [the cake pattern](http://jonasboner.com/2008/10/06/real-world-scala-dependency-injection-di/).
### Внедрение зависимостей
На своем опыте мы обнаружили, что Scala на самом деле удаляет большую часть синтаксических издержек "классического" (в конструкторе) внедрения зависимостей, и мы можем это использовать это: более ясное описание, зависимости все еще закодированы в типе, а конструкция класса так синтаксически проста, что это становится незаметно.
Это скучно и просто, и это работает. *Используйте внедрение зависимостей для модульного построения программы*, и в частности *предпочитайте композицию наследованию* - это приведет к более модульным и легко тестируемым программам.
Если возникает ситуация, когда требуется наследование, то спросите себя: как бы вы спроектировали программу, если бы язык не имел такой возможности, как наследование? Ответ может быть не так прост.
Для внедрения зависимостей обычно используются трейты(traits),
trait TweetStream {
def subscribe(f: Tweet => Unit)
}
class HosebirdStream extends TweetStream ...
class FileStream extends TweetStream ..
class TweetCounter(stream: TweetStream) {
stream.subscribe { tweet => count += 1 }
}
Они обычно исопользуются для внедрения *фабрик* - объектов, которые генерируют другие объекты. В этом случае предпочтительно использовать более простые функции вместо специализированных
фабрик типов.
class FilteredTweetCounter(mkStream: Filter => TweetStream) {
mkStream(PublicTweets).subscribe { tweet => publicCount += 1 }
mkStream(DMs).subscribe { tweet => dmCount += 1 }
}
### Трейты(Traits)
Внедрение зависимостей вовсе не исключает возможности использования общих *интерфейсов*, или релизации общего кода в трейтах. Совсем наоборот - использование трейтов настоятельно рекомендуется именно по этой причине: несколько интерфейсов (трейтов) могут быть реализованы в конкретном классе, а общий код может быть использован во всех таких классах.
Старайтесь писать трейты короткими и простыми: но не стоит разделять функциональности между трейтами, думайте о них, как о небольших кусочках, которые связаны друг с другом. Например, представьте, что у вас есть то, что может сделать IO:
trait IOer {
def write(bytes: Array[Byte])
def read(n: Int): Array[Byte]
}
.LP разделите код на два поведения:
trait Reader {
def read(n: Int): Array[Byte]
}
trait Writer {
def write(bytes: Array[Byte])
}
.LP или объедините их вместе, чтобы получить то, что было в <code>IOer</code>: <code> new Reader with Writer</code>… минимализм трейта приводит к простоте и более "чистой" модульности.
### Видимость
Скала имеет очень выразительные модификаторы видимости. Важно использовать их, так как они определяют то, что представляет собой *публичный API*. Публичные API должны быть ограничены так, чтобы пользователи случайно не полагались на реализацию деталей и предел возможностей автора менять их: они имеют решающее значение при постороение хорошей модульности. Как правило, гораздо проще расширять публичные API, чем их сокращать. Плохие аннотации также могут подорвать обратную
бинарную совместимость вашего кода.
#### `private[this]`
Член класса помеченный как `private`,
private val x: Int = ...
.LP видим для всех <em>экземпляров</em> этого класса (но не и подклассов). В большинстве случаев вам нужен <code>private[this]</code>.
private[this] val: Int = ..
.LP который ограничивает видимость для конкретного экземпляра.Компилятор Scala также может переводить <code>private[this]</code> в простое поле для доступа (поскольку доступ ограничен для статически определенного класса), которое иногда может помочь при оптимизации производительности.
#### Класс синглтон (Singleton)
Общепринятая в Scala практика по созданию класса синглтона, например
def foo() = new Foo with Bar with Baz {
...
}
.LP В таких ситуациях, видимость может быть ограничена, при объявлении возвращаемого типа:
def foo(): Foo with Bar = new Foo with Bar with Baz {
...
}
.LP где вызывающие <code>foo()</code> участки кода будут ограничены с помощью (<code>Foo with Bar</code>) для возвращаемого экземпляра.
### Структурная типизация
Не используйте структурных типов в обычных случаях. Они являются удобным и мощным средством, но к сожалению не имеют эффективной реализации для JVM. Однако - в связи с особенностями реализации - они обеспечивают очень краткое выражение для написания отражений(reflection).
val obj: AnyRef
obj.asInstanceOf[{def close()}].close()
## Сборка мусора
Мы тратим много времени на настройку сборщика мусора в реальном коде. Проблемы сборщика мусора в значительной степени аналогичны проблемам в Java, хотя с идеалогической точки зрения код Scala имеет тенденцию генерировать больше (короткоживущего) мусора, чем подобный код на Java - все это побочный продукт функционального стиля. Сборщик мусора обычно делает это
без проблем так как короткоживущий мусор эффективно собирается в большинстве случаев.
Прежде чем пытаться бороться с проблемами производительности сборщика мусора, посмотрите [это](http://www.infoq.com/presentations/JVM-Performance-Tuning-twitter) Выступление Аттилы, который иллюстрирует некоторый наш опыт по настройке сборщика мусора.
В Scala собственно вашим единственным инструментом облегчения проблем со сборщиком мусора является создание меньшего количества мусора, но не стоит дествовать без предварительных данных! Пока вы не сделали того, что явно ухудшит ситуацию, используйте различные инструменты профилирования Java - Наши собственные инструменты включают [heapster](https://github.com/mariusaeriksen/heapster) и [gcprof](https://github.com/twitter/jvmgcprof).
## Java совместимость
Когда мы пишем код на Scala, который используется в Java, мы уверены, что эта возможность осталась из чисто идеалогическох соображений. Обычно для этого не требуется лишних усилий - классы и чистые трейты в точности эквивалентны
их Java коллегам - но иногда нужно предоставить некоторые Java API. Хороший способ получить это для вашего библиотечного Java API, это написать юнит тест в Java (только для компиляции), это также обеспечит то, что поведение вашей Java библиотеки остается стабильным с течением времени, потому что компилятор Scala может быть неустойчив в этом отношении.
Трейты, которые реализуют некоторый функционал, не