├── .github └── workflows │ └── main.yml ├── .gitignore ├── Part 1 Good code ├── Chapter 1 Safety │ ├── Introduction.md │ ├── Item 1 Limit mutability.md │ ├── Item 10 Write unit tests.md │ ├── Item 2 Minimize the scope of variables.md │ ├── Item 3 Eliminate platform types as soon as possible.md │ ├── Item 4 Do not expose inferred types.md │ ├── Item 5 Specify your expectations on arguments and state.md │ ├── Item 6 Prefer standard errors to custom ones.md │ ├── Item 7 Prefer null or Failure result when the lack of result is possible.md │ ├── Item 8 Handle nulls properly.md │ └── Item 9 Close resources with use.md └── Chapter 2 Readability │ ├── Introduction.md │ ├── Item 11 Design for readability.md │ ├── Item 12 Operator meaning should be consistent with its function name.md │ ├── Item 13 Avoid returning or operating on Unit?.md │ ├── Item 14 Specify the variable type when it is not clear.md │ ├── Item 15 Consider referencing receivers explicitly.md │ ├── Item 16 Properties should represent state not behavior.md │ ├── Item 17 Consider naming arguments.md │ └── Item 18 Respect coding conventions.md ├── Part 2 Code design ├── Chapter 3 Reusability │ ├── Introduction.md │ ├── Item 19 Do not repeat knowledge.md │ ├── Item 20 Do not repeat common algorithms.md │ ├── Item 21 Use property delegation to extract common property patterns.md │ ├── Item 22 Use generics when implementing common algorithms.md │ ├── Item 23 Avoid shadowing type parameters.md │ ├── Item 24 Consider variance for generic types.md │ └── Item 25 Reuse between different platforms by extracting common modules.md ├── Chapter 4 Abstraction design │ ├── Introduction.md │ ├── Item 26 Each function should be written in terms of a single level of abstraction.md │ ├── Item 27 Use abstraction to protect code against changes.md │ ├── Item 28 Specify API stability.md │ ├── Item 29 Consider wrapping external API.md │ ├── Item 30 Minimize elements visibility.md │ ├── Item 31 Define contract with documentation.md │ └── Item 32 Respect abstraction contracts.md ├── Chapter 5 Object creation │ ├── Introduction.md │ ├── Item 33 Consider factory functions instead of constructors.md │ ├── Item 34 Consider a primary constructor with named optional arguments.md │ └── Item 35 Consider defining a DSL for complex object creation.md └── Chapter 6 Class design │ ├── Introduction.md │ ├── Item 36 Prefer composition over inheritance.md │ ├── Item 37 Use the data modifier to represent a bundle of data.md │ ├── Item 38 Use function types instead of interfaces to pass operations and actions.md │ ├── Item 39 Prefer class hierarchies to tagged classes.md │ ├── Item 40 Respect the contract of equals.md │ ├── Item 41 Respect the contract of hashCode.md │ ├── Item 42 Respect the contract of compareTo.md │ ├── Item 43 Consider extracting non-essential parts of your API into extensions.md │ └── Item 44 Avoid member extensions.md ├── Part 3 Efficiency ├── Chapter 7 Make it cheap │ ├── Introduction.md │ ├── Item 45 Avoid unnecessary object creation.md │ ├── Item 46 Use inline modifier for functions with parameters of functional types.md │ ├── Item 47 Consider using inline classes.md │ └── Item 48 Eliminate obsolete object references.md └── Chapter 8 Efficient collection processing │ ├── Introduction.md │ ├── Item 49 Prefer Sequence for big collections with more than one processing step.md │ ├── Item 50 Limit the number of operations.md │ ├── Item 51 Consider Arrays with primitives for performance-critical processing.md │ └── Item 52 Consider using mutable collections.md ├── README.md ├── SUMMARY.md ├── assets ├── chapter1 │ ├── chapter1-1.png │ ├── chapter1-2.png │ └── chapter1-3.png ├── chapter2 │ ├── chapter2-1.png │ └── chapter2-2.png ├── chapter3 │ ├── chapter3-1.png │ ├── chapter3-2.png │ ├── chapter3-3.png │ ├── chapter3-4.png │ ├── chapter3-5.png │ ├── chapter3-6.png │ ├── chapter3-7.png │ ├── chapter3-8.png │ └── chapter3-9.png ├── chapter4 │ ├── chapter4-1.png │ ├── chapter4-2.png │ ├── chapter4-3.png │ ├── chapter4-4.png │ ├── chapter4-5.png │ ├── chapter4-6.png │ ├── chapter4-7.png │ ├── chapter4-8.png │ └── chapter4-9.png ├── chapter5 │ ├── chapter5-1.png │ ├── chapter5-2.png │ └── chapter5-3.png ├── chapter6 │ ├── chapter6-1.png │ ├── chapter6-2.png │ ├── chapter6-3.png │ ├── chapter6-4.png │ └── chapter6-5.png ├── chapter7 │ ├── chapter7-1.png │ └── chapter7-2.png └── chapter8 │ ├── chapter8-1.png │ ├── chapter8-2.png │ ├── chapter8-3.png │ ├── chapter8-4.png │ ├── chapter8-5.png │ ├── chapter8-6.png │ └── chapter8-7.png ├── docs ├── .github │ └── workflows │ │ └── main.yml ├── Part 1 Good code │ ├── Chapter 1 Safety │ │ ├── Introduction.md │ │ ├── Item 1 Limit mutability.md │ │ ├── Item 10 Write unit tests.md │ │ ├── Item 2 Minimize the scope of variables.md │ │ ├── Item 3 Eliminate platform types as soon as possible.md │ │ ├── Item 4 Do not expose inferred types.md │ │ ├── Item 5 Specify your expectations on arguments and state.md │ │ ├── Item 6 Prefer standard errors to custom ones.md │ │ ├── Item 7 Prefer null or Failure result when the lack of result is possible.md │ │ ├── Item 8 Handle nulls properly.md │ │ └── Item 9 Close resources with use.md │ └── Chapter 2 Readability │ │ ├── Introduction.md │ │ ├── Item 11 Design for readability.md │ │ ├── Item 12 Operator meaning should be consistent with its function name.md │ │ ├── Item 13 Avoid returning or operating on Unit?.md │ │ ├── Item 14 Specify the variable type when it is not clear.md │ │ ├── Item 15 Consider referencing receivers explicitly.md │ │ ├── Item 16 Properties should represent state not behavior.md │ │ ├── Item 17 Consider naming arguments.md │ │ └── Item 18 Respect coding conventions.md ├── Part 2 Code design │ ├── Chapter 3 Reusability │ │ ├── Introduction.md │ │ ├── Item 19 Do not repeat knowledge.md │ │ ├── Item 20 Do not repeat common algorithms.md │ │ ├── Item 21 Use property delegation to extract common property patterns.md │ │ ├── Item 22 Use generics when implementing common algorithms.md │ │ ├── Item 23 Avoid shadowing type parameters.md │ │ ├── Item 24 Consider variance for generic types.md │ │ └── Item 25 Reuse between different platforms by extracting common modules.md │ ├── Chapter 4 Abstraction design │ │ ├── Introduction.md │ │ ├── Item 26 Each function should be written in terms of a single level of abstraction.md │ │ ├── Item 27 Use abstraction to protect code against changes.md │ │ ├── Item 28 Specify API stability.md │ │ ├── Item 29 Consider wrapping external API.md │ │ ├── Item 30 Minimize elements visibility.md │ │ ├── Item 31 Define contract with documentation.md │ │ └── Item 32 Respect abstraction contracts.md │ ├── Chapter 5 Object creation │ │ ├── Introduction.md │ │ ├── Item 33 Consider factory functions instead of constructors.md │ │ ├── Item 34 Consider a primary constructor with named optional arguments.md │ │ └── Item 35 Consider defining a DSL for complex object creation.md │ └── Chapter 6 Class design │ │ ├── Introduction.md │ │ ├── Item 36 Prefer composition over inheritance.md │ │ ├── Item 37 Use the data modifier to represent a bundle of data.md │ │ ├── Item 38 Use function types instead of interfaces to pass operations and actions.md │ │ ├── Item 39 Prefer class hierarchies to tagged classes.md │ │ ├── Item 40 Respect the contract of equals.md │ │ ├── Item 41 Respect the contract of hashCode.md │ │ ├── Item 42 Respect the contract of compareTo.md │ │ ├── Item 43 Consider extracting non-essential parts of your API into extensions.md │ │ └── Item 44 Avoid member extensions.md ├── Part 3 Efficiency │ ├── Chapter 7 Make it cheap │ │ ├── Introduction.md │ │ ├── Item 45 Avoid unnecessary object creation.md │ │ ├── Item 46 Use inline modifier for functions with parameters of functional types.md │ │ ├── Item 47 Consider using inline classes.md │ │ └── Item 48 Eliminate obsolete object references.md │ └── Chapter 8 Efficient collection processing │ │ ├── Introduction.md │ │ ├── Item 49 Prefer Sequence for big collections with more than one processing step.md │ │ ├── Item 50 Limit the number of operations.md │ │ ├── Item 51 Consider Arrays with primitives for performance-critical processing.md │ │ └── Item 52 Consider using mutable collections.md ├── assets │ ├── chapter1 │ │ ├── chapter1-1.png │ │ ├── chapter1-2.png │ │ └── chapter1-3.png │ ├── chapter2 │ │ ├── chapter2-1.png │ │ └── chapter2-2.png │ ├── chapter3 │ │ ├── chapter3-1.png │ │ ├── chapter3-2.png │ │ ├── chapter3-3.png │ │ ├── chapter3-4.png │ │ ├── chapter3-5.png │ │ ├── chapter3-6.png │ │ ├── chapter3-7.png │ │ ├── chapter3-8.png │ │ └── chapter3-9.png │ ├── chapter4 │ │ ├── chapter4-1.png │ │ ├── chapter4-2.png │ │ ├── chapter4-3.png │ │ ├── chapter4-4.png │ │ ├── chapter4-5.png │ │ ├── chapter4-6.png │ │ ├── chapter4-7.png │ │ ├── chapter4-8.png │ │ └── chapter4-9.png │ ├── chapter5 │ │ ├── chapter5-1.png │ │ ├── chapter5-2.png │ │ └── chapter5-3.png │ ├── chapter6 │ │ ├── chapter6-1.png │ │ ├── chapter6-2.png │ │ ├── chapter6-3.png │ │ ├── chapter6-4.png │ │ └── chapter6-5.png │ ├── chapter7 │ │ ├── chapter7-1.png │ │ └── chapter7-2.png │ └── chapter8 │ │ ├── chapter8-1.png │ │ ├── chapter8-2.png │ │ ├── chapter8-3.png │ │ ├── chapter8-4.png │ │ ├── chapter8-5.png │ │ ├── chapter8-6.png │ │ └── chapter8-7.png ├── gitbook │ ├── fonts │ │ └── fontawesome │ │ │ ├── FontAwesome.otf │ │ │ ├── fontawesome-webfont.eot │ │ │ ├── fontawesome-webfont.svg │ │ │ ├── fontawesome-webfont.ttf │ │ │ ├── fontawesome-webfont.woff │ │ │ └── fontawesome-webfont.woff2 │ ├── gitbook-plugin-fontsettings │ │ ├── fontsettings.js │ │ └── website.css │ ├── gitbook-plugin-highlight │ │ ├── ebook.css │ │ └── website.css │ ├── gitbook-plugin-lunr │ │ ├── lunr.min.js │ │ └── search-lunr.js │ ├── gitbook-plugin-search │ │ ├── lunr.min.js │ │ ├── search-engine.js │ │ ├── search.css │ │ └── search.js │ ├── gitbook-plugin-sharing │ │ └── buttons.js │ ├── gitbook.js │ ├── images │ │ ├── apple-touch-icon-precomposed-152.png │ │ └── favicon.ico │ ├── style.css │ └── theme.js ├── index.html ├── part-1-good-code │ ├── chapter-1-safety │ │ ├── di-1-tiao-xian-zhi-ke-bian-xing-part-1-good-codechapter-1-safetyitem-1-limit-mutability.md.html │ │ ├── di-10-tiao-bian-xie-dan-yuan-ce-shi-part-1-good-codechapter-1-safetyitem-10-write-unit-tests.md.html │ │ ├── di-2-tiao-zui-xiao-hua-bian-liang-zuo-yong-yu-part-1-good-codechapter-1-safetyitem-2-minimize-the-sc.html │ │ ├── di-3-tiao-jin-kuai-xiao-chu-ping-tai-lei-xing-part-1-good-codechapter-1-safetyitem-3-eliminate-platf.html │ │ ├── di-4-tiao-bu-yao-ba-tui-duan-lei-xing-bao-lou-gei-wai-bu-part-1-good-codechapter-1-safetyitem-4-do-n.html │ │ ├── di-5-tiao-zai-can-shu-yu-zhuang-tai-shang-zhi-ding-ni-de-qi-wang-part-1-good-codechapter-1-safetyite.html │ │ ├── di-6-tiao-jin-ke-neng-shi-yong-biao-zhun-ku-zhong-ti-gong-de-yi-chang-part-1-good-codechapter-1-safe.html │ │ ├── di-7-tiao-dang-bu-neng-fan-hui-yu-qi-jie-guo-shi-you-xian-shi-yong-nullohuo-failure-zuo-wei-fan-hui.html │ │ ├── di-8-tiao-zheng-que-di-chu-li-null-zhi-part-1-good-codechapter-1-safetyitem-8-handle-nulls-properly..html │ │ ├── di-9-tiao-shi-yong-use-guan-bi-zi-yuan-part-1-good-codechapter-1-safetyitem-9-close-resources-with-u.html │ │ ├── index.html │ │ └── yin-yan-part-1-good-codechapter-1-safetyintroduction.md.html │ ├── chapter-2-readability │ │ ├── di-11-tiao-ke-du-xing-she-ji-part-1-good-codechapter-2-readabilityitem-11-design-for-readability.md.html │ │ ├── di-12-tiao-cao-zuo-fu-de-han-yi-ying-yu-qi-han-shu-ming-yi-zhi-part-1-good-codechapter-2-readability.html │ │ ├── di-13-tiao-bi-mian-fan-hui-huo-cao-zuo-unit-part-1-good-codechapter-2-readabilityitem201320avoid20re.html │ │ ├── di-14-tiao-zai-lei-xing-bu-ming-que-de-qing-kuang-xia-qing-xian-shi-zhi-ding-bian-liang-de-lei-xing.html │ │ ├── di-15-tiao-kao-lv-ming-que-zhi-ding-jie-shou-zhe-part-1-good-codechapter-2-readabilityitem-15-consid.html │ │ ├── di-16-tiao-shu-xing-ying-dai-biao-zhuang-tai-er-bu-shi-hang-wei-part-1-good-codechapter-2-readabilit.html │ │ ├── di-17-tiao-kao-lv-ming-ming-can-shu-part-1-good-codechapter-2-readabilityitem-17-consider-naming-arg.html │ │ ├── di-18-tiao-zun-zhong-bian-ma-gui-fan-part-1-good-codechapter-2-readabilityitem-18-respect-coding-con.html │ │ ├── index.html │ │ └── yin-yan-part-1-good-codechapter-2-readabilityintroduction.md.html │ └── index.html ├── part-2-code-design │ ├── chapter-3-reusability │ │ ├── index.html │ │ ├── introduction-part-2-code-design-chapter-3-reusability-introduction.md.html │ │ ├── item-19-do-not-repeat-knowledge-part-2-code-design-chapter-3-reusability-item-19-do-not-repeat-knowl.html │ │ ├── item-20-do-not-repeat-common-algorithms-part-2-code-design-chapter-3-reusability-item-20-do-not-repe.html │ │ ├── item-21-use-property-delegation-to-extract-common-property-patterns-part-2-code-design-chapter-3-reu.html │ │ ├── item-22-use-generics-when-implementing-common-algorithms-part-2-code-design-chapter-3-reusability-it.html │ │ ├── item-23-avoid-shadowing-type-parameters-part-2-code-design-chapter-3-reusability-item-23-avoid-shado.html │ │ ├── item-24-consider-variance-for-generic-types-part-2-code-design-chapter-3-reusability-item-24-conside.html │ │ └── item-25-reuse-between-different-platforms-by-extracting-common-modules-part-2-code-design-chapter-3.html │ ├── chapter-4-abstraction-design │ │ ├── index.html │ │ ├── introduction-part-2-code-design-chapter-4-abstraction-design-introduction.md.html │ │ ├── item-26-each-function-should-be-written-in-terms-of-a-single-level-of-abstraction-part-2-code-design.html │ │ ├── item-27-use-abstraction-to-protect-code-against-changes-part-2-code-design-chapter-4-abstraction-des.html │ │ ├── item-28-specify-api-stability-part-2-code-design-chapter-4-abstraction-design-item-28-specify-api-st.html │ │ ├── item-29-consider-wrapping-external-api-part-2-code-design-chapter-4-abstraction-design-item-29-consi.html │ │ ├── item-30-minimize-elements-visibility-part-2-code-design-chapter-4-abstraction-design-item-30-minimiz.html │ │ ├── item-31-define-contract-with-documentation-part-2-code-design-chapter-4-abstraction-design-item-31-d.html │ │ └── item-32-respect-abstraction-contracts-part-2-code-design-chapter-4-abstraction-design-item-32-respec.html │ ├── chapter-5-object-creation │ │ ├── index.html │ │ ├── introduction-part-2-code-design-chapter-5-object-creation-introduction.md.html │ │ ├── item-33-consider-factory-functions-instead-of-constructors-part-2-code-design-chapter-5-object-creat.html │ │ ├── item-34-consider-a-primary-constructor-with-named-optional-arguments-part-2-code-design-chapter-5-ob.html │ │ └── item-35-consider-defining-a-dsl-for-complex-object-creation-part-2-code-design-chapter-5-object-crea.html │ ├── chapter-6-class-design │ │ ├── index.html │ │ ├── introduction-part-2-code-design-chapter-6-class-design-introduction.md.html │ │ ├── item-36-prefer-composition-over-inheritance-part-2-code-design-chapter-6-class-design-item-36-prefer.html │ │ ├── item-37-use-the-data-modifier-to-represent-a-bundle-of-data-part-2-code-design-chapter-6-class-desig.html │ │ ├── item-38-use-function-types-instead-of-interfaces-to-pass-operations-and-actions-part-2-code-design-c.html │ │ ├── item-39-prefer-class-hierarchies-to-tagged-classes-part-2-code-design-chapter-6-class-design-item-39.html │ │ ├── item-40-respect-the-contract-of-equals-part-2-code-design-chapter-6-class-design-item-40-respect-the.html │ │ ├── item-41-respect-the-contract-of-hash-code-part-2-code-design-chapter-6-class-design-item-41-respect.html │ │ ├── item-42-respect-the-contract-of-compare-to-part-2-code-design-chapter-6-class-design-item-42-respect.html │ │ ├── item-43-consider-extracting-non-essential-parts-of-your-api-into-extensions-part-2-code-design-chapt.html │ │ └── item-44-avoid-member-extensions-part-2-code-design-chapter-6-class-design-item-44-avoid-member-exten.html │ └── index.html ├── part-3-efficiency │ ├── chapter-7-make-it-cheap │ │ ├── index.html │ │ ├── introduction-part-3-efficiency-chapter-7-make-it-cheap-introduction.md.html │ │ ├── item-45-avoid-unnecessary-object-creation-part-3-efficiency-chapter-7-make-it-cheap-item-45-avoid-un.html │ │ ├── item-46-use-inline-modifier-for-functions-with-parameters-of-functional-types-part-3-efficiency-chap.html │ │ ├── item-47-consider-using-inline-classes-part-3-efficiency-chapter-7-make-it-cheap-item-47-consider-usi.html │ │ └── item-48-eliminate-obsolete-object-references-part-3-efficiency-chapter-7-make-it-cheap-item-48-elimi.html │ ├── chapter-8-efficient-collection-processing │ │ ├── di-50-tiao-jian-shao-cao-zuo-de-ci-shu-part-3-efficiencychapter-8-efficient-collection-processingite.html │ │ ├── di-51-tiao-zai-xing-neng-you-xian-de-chang-jing-kao-lv-shi-yong-ji-chu-lei-xing-shu-zu-part-3-effici.html │ │ ├── di-52-tiao-zai-chu-li-ju-bu-bian-liang-shi-kao-lv-shi-yong-ke-bian-ji-he-part-3-efficiencychapter-8.html │ │ ├── index.html │ │ ├── introduction-part-3-efficiency-chapter-8-efficient-collection-processing-introduction.md.html │ │ └── item-49-prefer-sequence-for-big-collections-with-more-than-one-processing-step-part-3-efficiency-cha.html │ └── index.html └── search_index.json ├── part-1-good-code ├── README.md ├── chapter-1-safety │ ├── README.md │ ├── di-1-tiao-xian-zhi-ke-bian-xing-part-1-good-codechapter-1-safetyitem-1-limit-mutability.md.md │ ├── di-10-tiao-bian-xie-dan-yuan-ce-shi-part-1-good-codechapter-1-safetyitem-10-write-unit-tests.md.md │ ├── di-2-tiao-zui-xiao-hua-bian-liang-zuo-yong-yu-part-1-good-codechapter-1-safetyitem-2-minimize-the-sc.md │ ├── di-3-tiao-jin-kuai-xiao-chu-ping-tai-lei-xing-part-1-good-codechapter-1-safetyitem-3-eliminate-platf.md │ ├── di-4-tiao-bu-yao-ba-tui-duan-lei-xing-bao-lou-gei-wai-bu-part-1-good-codechapter-1-safetyitem-4-do-n.md │ ├── di-5-tiao-zai-can-shu-yu-zhuang-tai-shang-zhi-ding-ni-de-qi-wang-part-1-good-codechapter-1-safetyite.md │ ├── di-6-tiao-jin-ke-neng-shi-yong-biao-zhun-ku-zhong-ti-gong-de-yi-chang-part-1-good-codechapter-1-safe.md │ ├── di-7-tiao-dang-bu-neng-fan-hui-yu-qi-jie-guo-shi-you-xian-shi-yong-nullohuo-failure-zuo-wei-fan-hui.md │ ├── di-8-tiao-zheng-que-di-chu-li-null-zhi-part-1-good-codechapter-1-safetyitem-8-handle-nulls-properly..md │ ├── di-9-tiao-shi-yong-use-guan-bi-zi-yuan-part-1-good-codechapter-1-safetyitem-9-close-resources-with-u.md │ └── yin-yan-part-1-good-codechapter-1-safetyintroduction.md.md └── chapter-2-readability │ ├── README.md │ ├── di-11-tiao-ke-du-xing-she-ji-part-1-good-codechapter-2-readabilityitem-11-design-for-readability.md.md │ ├── di-12-tiao-cao-zuo-fu-de-han-yi-ying-yu-qi-han-shu-ming-yi-zhi-part-1-good-codechapter-2-readability.md │ ├── di-13-tiao-bi-mian-fan-hui-huo-cao-zuo-unit-part-1-good-codechapter-2-readabilityitem201320avoid20re.md │ ├── di-14-tiao-zai-lei-xing-bu-ming-que-de-qing-kuang-xia-qing-xian-shi-zhi-ding-bian-liang-de-lei-xing.md │ ├── di-15-tiao-kao-lv-ming-que-zhi-ding-jie-shou-zhe-part-1-good-codechapter-2-readabilityitem-15-consid.md │ ├── di-16-tiao-shu-xing-ying-dai-biao-zhuang-tai-er-bu-shi-hang-wei-part-1-good-codechapter-2-readabilit.md │ ├── di-17-tiao-kao-lv-ming-ming-can-shu-part-1-good-codechapter-2-readabilityitem-17-consider-naming-arg.md │ ├── di-18-tiao-zun-zhong-bian-ma-gui-fan-part-1-good-codechapter-2-readabilityitem-18-respect-coding-con.md │ └── yin-yan-part-1-good-codechapter-2-readabilityintroduction.md.md ├── part-2-code-design ├── README.md ├── chapter-3-reusability │ ├── README.md │ ├── introduction-part-2-code-design-chapter-3-reusability-introduction.md.md │ ├── item-19-do-not-repeat-knowledge-part-2-code-design-chapter-3-reusability-item-19-do-not-repeat-knowl.md │ ├── item-20-do-not-repeat-common-algorithms-part-2-code-design-chapter-3-reusability-item-20-do-not-repe.md │ ├── item-21-use-property-delegation-to-extract-common-property-patterns-part-2-code-design-chapter-3-reu.md │ ├── item-22-use-generics-when-implementing-common-algorithms-part-2-code-design-chapter-3-reusability-it.md │ ├── item-23-avoid-shadowing-type-parameters-part-2-code-design-chapter-3-reusability-item-23-avoid-shado.md │ ├── item-24-consider-variance-for-generic-types-part-2-code-design-chapter-3-reusability-item-24-conside.md │ └── item-25-reuse-between-different-platforms-by-extracting-common-modules-part-2-code-design-chapter-3.md ├── chapter-4-abstraction-design │ ├── README.md │ ├── introduction-part-2-code-design-chapter-4-abstraction-design-introduction.md.md │ ├── item-26-each-function-should-be-written-in-terms-of-a-single-level-of-abstraction-part-2-code-design.md │ ├── item-27-use-abstraction-to-protect-code-against-changes-part-2-code-design-chapter-4-abstraction-des.md │ ├── item-28-specify-api-stability-part-2-code-design-chapter-4-abstraction-design-item-28-specify-api-st.md │ ├── item-29-consider-wrapping-external-api-part-2-code-design-chapter-4-abstraction-design-item-29-consi.md │ ├── item-30-minimize-elements-visibility-part-2-code-design-chapter-4-abstraction-design-item-30-minimiz.md │ ├── item-31-define-contract-with-documentation-part-2-code-design-chapter-4-abstraction-design-item-31-d.md │ └── item-32-respect-abstraction-contracts-part-2-code-design-chapter-4-abstraction-design-item-32-respec.md ├── chapter-5-object-creation │ ├── README.md │ ├── introduction-part-2-code-design-chapter-5-object-creation-introduction.md.md │ ├── item-33-consider-factory-functions-instead-of-constructors-part-2-code-design-chapter-5-object-creat.md │ ├── item-34-consider-a-primary-constructor-with-named-optional-arguments-part-2-code-design-chapter-5-ob.md │ └── item-35-consider-defining-a-dsl-for-complex-object-creation-part-2-code-design-chapter-5-object-crea.md └── chapter-6-class-design │ ├── README.md │ ├── introduction-part-2-code-design-chapter-6-class-design-introduction.md.md │ ├── item-36-prefer-composition-over-inheritance-part-2-code-design-chapter-6-class-design-item-36-prefer.md │ ├── item-37-use-the-data-modifier-to-represent-a-bundle-of-data-part-2-code-design-chapter-6-class-desig.md │ ├── item-38-use-function-types-instead-of-interfaces-to-pass-operations-and-actions-part-2-code-design-c.md │ ├── item-39-prefer-class-hierarchies-to-tagged-classes-part-2-code-design-chapter-6-class-design-item-39.md │ ├── item-40-respect-the-contract-of-equals-part-2-code-design-chapter-6-class-design-item-40-respect-the.md │ ├── item-41-respect-the-contract-of-hash-code-part-2-code-design-chapter-6-class-design-item-41-respect.md │ ├── item-42-respect-the-contract-of-compare-to-part-2-code-design-chapter-6-class-design-item-42-respect.md │ ├── item-43-consider-extracting-non-essential-parts-of-your-api-into-extensions-part-2-code-design-chapt.md │ └── item-44-avoid-member-extensions-part-2-code-design-chapter-6-class-design-item-44-avoid-member-exten.md └── part-3-efficiency ├── README.md ├── chapter-7-make-it-cheap ├── README.md ├── introduction-part-3-efficiency-chapter-7-make-it-cheap-introduction.md.md ├── item-45-avoid-unnecessary-object-creation-part-3-efficiency-chapter-7-make-it-cheap-item-45-avoid-un.md ├── item-46-use-inline-modifier-for-functions-with-parameters-of-functional-types-part-3-efficiency-chap.md ├── item-47-consider-using-inline-classes-part-3-efficiency-chapter-7-make-it-cheap-item-47-consider-usi.md └── item-48-eliminate-obsolete-object-references-part-3-efficiency-chapter-7-make-it-cheap-item-48-elimi.md └── chapter-8-efficient-collection-processing ├── README.md ├── di-50-tiao-jian-shao-cao-zuo-de-ci-shu-part-3-efficiencychapter-8-efficient-collection-processingite.md ├── di-51-tiao-zai-xing-neng-you-xian-de-chang-jing-kao-lv-shi-yong-ji-chu-lei-xing-shu-zu-part-3-effici.md ├── di-52-tiao-zai-chu-li-ju-bu-bian-liang-shi-kao-lv-shi-yong-ke-bian-ji-he-part-3-efficiencychapter-8.md ├── introduction-part-3-efficiency-chapter-8-efficient-collection-processing-introduction.md.md └── item-49-prefer-sequence-for-big-collections-with-more-than-one-processing-step-part-3-efficiency-cha.md /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: auto-generate-gitbook 2 | on: 3 | push: 4 | branches: 5 | - main 6 | 7 | jobs: 8 | main-to-gh-pages: 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | - name: checkout main 13 | uses: actions/checkout@v2 14 | with: 15 | ref: main 16 | 17 | - name: install nodejs 18 | uses: actions/setup-node@v1 19 | 20 | - name: configue gitbook 21 | run: | 22 | npm install -g gitbook-cli 23 | gitbook install 24 | 25 | - name: build book 26 | run: | 27 | gitbook build . docs 28 | 29 | - name: publish book 30 | env: 31 | TOKEN: ${{ secrets.TOKEN }} 32 | REF: github.com/${{github.repository}} 33 | MYEMAIL: MaxzMeng@gmail.com 34 | MYNAME: ${{github.repository_owner}} 35 | run: | 36 | git config --global user.email "${MYEMAIL}" 37 | git config --global user.name "${MYNAME}" 38 | git init 39 | git add . 40 | git commit -m "Updated By Github Actions With Build ${{github.run_number}} of ${{github.workflow}} For Github Pages" 41 | git push 42 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gitignore 2 | .idea 3 | node_modules 4 | .DS_Store 5 | -------------------------------------------------------------------------------- /Part 1 Good code/Chapter 1 Safety/Introduction.md: -------------------------------------------------------------------------------- 1 | 是什么让我们决定在我们的项目中使用 Kotlin 而不是 Java、JavaScript 或 C++? 对于开发人员来说,他们经常被Kotlin简洁或惊人的特性所吸引。 而对于商用层面,我发现Kotlin 安全性是吸引大家使用它的强有力的支撑——它的设计消除了潜在的应用程序错误。 当您使用的应用程序崩溃时,或者当您花了一个小时把物品添加到购物车后网站出现错误从而导致无法结帐时,您即使是一名普通的用户都会感到十分懊恼。 更少的崩溃使用户和开发人员的生活变得更好,并提供了重要的商业价值。 2 | 3 | 安全性对我们来说很重要,Kotlin 是一种真正安全的语言,但它仍然需要开发人员的支持才能保证真正的安全。在本章中,我们将讨论 Kotlin 中最重要的安全性的最佳实践。我们将看到 Kotlin 本身的特性是如何提高安全性的,以及我们如何正确使用它们。本章中每一项的目的都是为了生成不易出错的代码。 -------------------------------------------------------------------------------- /Part 1 Good code/Chapter 1 Safety/Item 10 Write unit tests.md: -------------------------------------------------------------------------------- 1 | ## 第10条:编写单元测试 2 | 3 | 在这一章中,你已经了解了许多使代码更安全的方法,但实现这一目标的最终方式是使用不同类型的测试。其中一种类型是检查应用程序从用户角度的行为是否正确。这种类型的测试通常是管理层唯一认可的,因为他们的主要目标是确保应用程序在外部行为正确。这些测试甚至不需要开发人员参与,而是可以由足够数量的测试人员处理,或者更好的选择是由测试工程师编写的自动化测试。 4 | 5 | 这些测试对于程序员来说很有用,但它们还不足够。它们不能提供对系统具体元素行为正确性的充分保证。它们也不能提供在开发过程中有用的快速反馈。为了实现这一点,我们需要一种不同类型的测试,对开发人员来说更加有用,由开发人员编写:单元测试。以下是一个示例单元测试,检查我们的 `fib` 函数在前5个位置上计算斐波那契数是否给出正确的结果: 6 | 7 | ``` kotlin 8 | @Test 9 | fun `fib works correctly for the first 5 positions`() { 10 | assertEquals(1, fib(0)) 11 | assertEquals(1, fib(1)) 12 | assertEquals(2, fib(2)) 13 | assertEquals(3, fib(3)) 14 | assertEquals(5, fib(4)) 15 | } 16 | ``` 17 | 18 | 使用单元测试时,我们通常会检查以下内容: 19 | 20 | - 常见用例(正常流程):检查我们预期元素被使用的典型方式。就像上面的示例中一样,我们测试函数在一些小数字上的工作情况。 21 | - 常见错误情况或潜在问题:测试我们认为可能无法正常工作或在过去显示存在问题的情况。 22 | - 边缘情况和非法参数:例如,对于 `Int` 类型,我们可以检查非常大的数字,如 `Int.MAX_VALUE`。对于可为空的对象,可能是 `null` 或填充了 `null` 值的对象。斐波那契数列中没有负数位置的数字,因此我们可以检查该函数在这种情况下的行为。 23 | 24 | 单元测试在开发过程中非常有用,因为它们可以快速反馈我们实现的元素如何工作。测试会不断累积,因此您可以轻松检查是否存在回归。它们还可以检查手动测试难以覆盖的情况。甚至有一种称为测试驱动开发(Test Driven Development,TDD)的方法,我们先编写一个单元测试,然后再编写实现以满足该测试。 25 | 26 | 单元测试带来的最大优势包括: 27 | 28 | 1. 可靠性提升:经过充分测试的元素通常更可靠。同时,心理上也会有安全感。当元素经过充分测试时,我们在操作它们时更有信心。 29 | 30 | 1. 重构的便利性:当元素经过适当的测试时,我们不再害怕对其进行重构。因此,经过充分测试的程序往往会逐渐改善。相比之下,没有测试的程序会让开发人员不敢轻易触碰遗留代码,因为他们可能会无意中引入错误。 31 | 32 | 1. 快速反馈循环:使用单元测试来检查元素是否正确工作通常比手动验证更快。更快的反馈循环加快了开发速度,使其更加愉快。它还有助于降低修复错误的成本:发现错误越早,修复成本越低。 33 | 34 | 当然,单元测试也存在一些缺点: 35 | 36 | 1. 编写单元测试需要时间。尽管从长远来看,良好的单元测试可以节省我们的时间,因为我们在后续的调试和查找错误过程中花费的时间更少。运行单元测试也比手动测试或其他类型的自动化测试快很多,从而节省了大量时间。 37 | 38 | 1. 需要调整代码以使其具备可测试性。这些更改通常很困难,但它们通常也迫使开发人员使用良好和成熟的架构。 39 | 40 | 1. 编写好的单元测试很困难。这需要一定的技能和理解,这些技能与其他开发工作有所不同。编写质量低劣的单元测试可能会带来负面影响。每个人都需要学习如何正确地为自己的代码编写单元测试,最好先参加软件测试或测试驱动开发(TDD)的课程。 41 | 42 | 最大的挑战是获得有效进行单元测试并编写支持单元测试的代码的技能。经验丰富的 Kotlin 开发人员应该掌握这些技能,并至少对代码的重要部分进行单元测试,包括: 43 | 44 | - 复杂功能 45 | - 预计会随时间变化或进行重构的部分 46 | - 业务逻辑 47 | - 公共 API 的部分 48 | - 容易出现问题的部分 49 | - 我们修复的生产错误 50 | 51 | 我们也不需要止步于此。测试是对应用程序可靠性和长期可维护性的一种投资。 52 | 53 | ### 总结 54 | 55 | 本章以一个反思开始,即我们程序的首要任务应该是正确地运行。通过使用本章介绍的良好实践可以支持这一点,但最重要的是,确保我们的应用程序正确运行的最佳方法是通过测试来检查,尤其是单元测试。这就是为什么一个负责任的关于安全性的章节至少需要包含一个关于单元测试的简短部分的原因。就像负责任的商业应用程序需要至少一些单元测试一样。 -------------------------------------------------------------------------------- /Part 1 Good code/Chapter 1 Safety/Item 2 Minimize the scope of variables.md: -------------------------------------------------------------------------------- 1 | ## 第2条:最小化变量作用域 2 | 3 | 当我们定义一个状态时,我们倾向于通过以下方式收紧变量和属性的范围: 4 | 5 | - 使用局部变量代替属性 6 | - 在尽可能小的范围内使用变量,例如,如果一个变量只在循环中使用,那么就在这个循环中定义它 7 | 8 | 元素的作用域是指计算机程序中该元素可见的区域。在Kotlin中,作用域几乎总是由花括号创建的,我们通常可以从作用域内部访问外部的元素。看看这个例子: 9 | 10 | ``` kotlin 11 | val a = 1 12 | fun fizz() { 13 | val b = 2 14 | print(a + b) 15 | } 16 | val buzz = { 17 | val c = 3 18 | print(a + c) 19 | } 20 | // 这里可以访问到 a ,但是无法访问 b 或 c 21 | ``` 22 | 23 | 在上面的例子中,在函数` fizz `和` buzz `的作用域中,我们可以从函数作用域中访问外部作用域的变量。但是,在外部作用域中,我们不能访问这些函数中定义的变量。下面是一个限制变量作用域的示例: 24 | 25 | ``` kotlin 26 | // 不好的写法 27 | var user: User 28 | for (i in users.indices) { 29 | user = users[i] 30 | print("User at $i is $user") 31 | } 32 | 33 | // 较好的写法 34 | for (i in users.indices) { 35 | val user = users[i] 36 | print("User at $i is $user") 37 | } 38 | 39 | // 相同的变量作用域,更好的语法 40 | for ((i, user) in users.withIndex()) { 41 | print("User at $i is $user") 42 | } 43 | ``` 44 | 45 | 在第一个例子中,` user ` 变量不仅在for循环的范围内可以访问,而且在for循环之外也可以访问。在第二个和第三个例子中,我们将变量` user ` 的作用域具体限制为for循环的作用域。 46 | 47 | 类似地,在作用域中可能还有许多作用域(最有可能的是嵌套在lambda表达式中的lambda表达式创建的),最好在尽可能小的范围内定义变量。 48 | 49 | 我们这么做原因有很多,但最重要的是:**当我们收紧变量的作用域时,就会使我们的程序易于调试和管理。**当我们分析代码时,我们需要考虑此时存在哪些元素。需要处理的元素越多,编程就越困难。应用程序越简单,它崩溃的可能性就越小。这也是为什么我们更喜欢不可变属性或对象。 50 | 51 | **考虑可变属性,当它们只能在较小的范围内修改时,更容易跟踪它们如何更改。**更容易对他们进行推理并改变他们的行为。 52 | 53 | 另一个问题是,**具有更大范围的变量可能会被其他开发人员过度使用**。例如,有人可能认为,如果使用一个变量来为迭代中的下一个元素赋值,那么在循环完成后,列表中的最后一个元素应该保留在该变量中。这样的推理可能导致严重的滥用,比如在迭代之后使用这个变量对最后一个元素做一些事情。这将是非常糟糕的,因为当另一个开发人员试图理解这个值的含义时,就需要理解整个执行过程。这将是一个不必要的麻烦。 54 | 55 | **无论变量是只读的还是可读写的,我们总是倾向于在定义变量时就对其进行初始化。**不要强迫开发人员查看它的定义位置。这可以通过控制结构语句来实现,例如if, when, try-catch或Elvis操作符用作表达式: 56 | 57 | ``` kotlin 58 | // 不好的写法 59 | val user: User 60 | if (hasValue) { 61 | user = getValue() 62 | } else { 63 | user = User() 64 | } 65 | 66 | // 较好的写法 67 | val user: User = if(hasValue) { 68 | getValue() 69 | } else { 70 | User() 71 | } 72 | ``` 73 | 74 | 如果我们需要设置多个属性,解构声明可以帮助我们更好的实现: 75 | 76 | ``` kotlin 77 | // 不好的写法 78 | fun updateWeather(degrees: Int) { 79 | val description: String 80 | val color: Int 81 | if (degrees < 5) { 82 | description = "cold" 83 | color = Color.BLUE 84 | } else if (degrees < 23) { 85 | description = "mild" 86 | color = Color.YELLOW 87 | } else { 88 | description = "hot" 89 | color = Color.RED 90 | } 91 | // ... 92 | } 93 | 94 | // 较好的写法 95 | fun updateWeather(degrees: Int) { 96 | val (description, color) = when { 97 | degrees < 5 -> "cold" to Color.BLUE 98 | degrees < 23 -> "mild" to Color.YELLOW 99 | else -> "hot" to Color.RED 100 | } 101 | // ... 102 | } 103 | ``` 104 | 105 | 最后,太大的变量范围可能是危险的。让我们来看一个例子。 106 | 107 | ### 变量捕获 108 | 109 | 当我在教授 Kotlin 协程时,我布置的练习之一是使用序列构建器实现 Eratosthenes 算法以查找素数。 该算法在概念上很简单: 110 | 111 | 1. 创建一个从 2 开始的数字列表。 112 | 2. 取第一个数,它是一个素数。 113 | 3. 从其余的数字中,我们删除第一个数字,并过滤掉所有可以被这个素数整除的数字。 114 | 115 | 该算法的一个简单实现如下所示: 116 | 117 | ``` kotlin 118 | var numbers = (2..100).toList() 119 | val primes = mutableListOf() 120 | while (numbers.isNotEmpty()) { 121 | val prime = numbers.first() 122 | primes.add(prime) 123 | numbers = numbers.filter { it % prime != 0 } 124 | } 125 | print(primes) // [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 126 | // 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97] 127 | ``` 128 | 129 | 这个问题的挑战在于如何让它产生一个可能无限的质数序列。 如果您想挑战自己,请立即停止阅读并尝试实现它。 130 | 131 | 这是一种解决方法: 132 | 133 | ``` kotlin 134 | val primes: Sequence = sequence { 135 | var numbers = generateSequence(2) { it + 1 } 136 | 137 | while (true) { 138 | val prime = numbers.first() 139 | yield(prime) 140 | numbers = numbers.drop(1) 141 | .filter { it % prime != 0 } 142 | } 143 | } 144 | 145 | print(primes.take(10).toList()) 146 | // [2, 3, 5, 7, 11, 13, 17, 19, 23, 29] 147 | ``` 148 | 149 | 在几乎每一组中都有一个人试图“优化”它。他们提取```prime```作为可变变量,而不是在每个循环中都创建变量: 150 | 151 | ``` kotlin 152 | val primes: Sequence = sequence { 153 | var numbers = generateSequence(2) { it + 1 } 154 | 155 | var prime: Int 156 | while (true) { 157 | prime = numbers.first() 158 | yield(prime) 159 | numbers = numbers.drop(1) 160 | .filter { it % prime != 0 } 161 | } 162 | } 163 | ``` 164 | 165 | 但是这会导致这个实现不再能得到正确的结果。以下是前10个数字: 166 | 167 | ``` kotlin 168 | print(primes.take(10).toList()) 169 | // [2, 3, 5, 6, 7, 8, 9, 10, 11, 12] 170 | ``` 171 | 172 | 现在你可以停下来去尝试解释为什么会出现这样的结果。 173 | 174 | 我们得到这样结果的原因是我们捕获了变量`prime`。因为我们使用的是序列,所以过滤是惰性完成的。在每一步中,我们不断地在添加过滤器。而在“优化”版本的代码中,我们总是只添加引用可变属性`prime`的过滤器。 因此,我们总是过滤` prime`的最后一个值用来过滤。 这就是为什么我们不能过滤出正确的结果。只有drop函数生效了,所以我们得到的是一个连续的数字序列 (除了`prime `被设置为2时被过滤掉的4). 175 | 176 | 我们应该意识到这种无意捕获的问题,因为这种情况时有发生。为了防止这种情况,我们应该避免可变性,并使变量的作用域更小。 177 | 178 | ### 总结 179 | 180 | 出于许多原因,我们应该更倾向于在最小的范围内定义变量。同样,对于局部变量,我们更喜欢` val` 而不是`var `。我们应该始终意识到变量是在lambdas表达式中被捕获的。这些简单的规则可以为我们省去许多麻烦。 181 | -------------------------------------------------------------------------------- /Part 1 Good code/Chapter 1 Safety/Item 4 Do not expose inferred types.md: -------------------------------------------------------------------------------- 1 | ## 第4条:不要把推断类型暴露给外部 2 | 3 | Kotlin的类型推断是JVM中最流行的Kotlin特性之一。以至于Java 10也引入了类型推断(与Kotlin相比有限)。 不过,使用该特性也有一些危险性。最重要的是,我们需要记住,赋值的推断类型是右边的确切类型,而不是超类或接口类型: 4 | 5 | ```kotlin 6 | open class Animal 7 | class Zebra: Animal() 8 | 9 | fun main() { 10 | var animal = Zebra() 11 | animal = Animal() // Error: Type mismatch 12 | } 13 | ``` 14 | 15 | 在大多数情况下,这都不是一个难题。当我们需要限定推断出的类型时,我们只需要指定它,问题就解决了: 16 | 17 | ``` kotlin 18 | open class Animal 19 | class Zebra: Animal() 20 | 21 | fun main() { 22 | var animal: Animal = Zebra() 23 | animal = Animal() 24 | } 25 | ``` 26 | 27 | 然而,当我们使用三方编写的一个库或另一个模块时,就没有这么容易了。在这种情况下,推断类型说明可能非常危险。让我们看一个例子。 28 | 29 | 假设你有以下接口用来代表汽车工厂: 30 | 31 | ``` kotlin 32 | interface CarFactory { 33 | fun produce(): Car 34 | } 35 | ``` 36 | 37 | 如果没有指定其他参数,也会使用默认的类型`Fiat126P`: 38 | 39 | ``` kotlin 40 | val DEFAULT_CAR: Car = Fiat126P() 41 | ``` 42 | 43 | 因为绝大多数汽车工厂都可以生产它,所以你把它设为默认值。你没有给它声明返回值类型,因为你认为`DEFAULT_CAR `无论如何都是`Car`的实例: 44 | 45 | ``` kotlin 46 | interface CarFactory { 47 | fun produce() = DEFAULT_CAR 48 | } 49 | ``` 50 | 51 | 类似地, 后来有其他人看到 `DEFAULT_CAR`的声明并且认为它的类型能够被推断出来: 52 | 53 | ``` kotlin 54 | val DEFAULT_CAR = Fiat126P() 55 | ``` 56 | 57 | 现在你会发现所有的工厂都只能生产`Fiat126P`。这显然是有问题的。如果这个接口是你自己定义的,那么这个问题可能很快就会被发现并且很容易修复。但是,如果它作为外部API被提供给用户使用,你可能会从愤怒的用户那里得知这个问题。 58 | 59 | 除此之外,当某人不太了解API时,返回类型是很重要的信息。因此为了可读性,我们应该显式声明返回类型,特别是在我们的API的外部可见部分(即公开的API)中。 60 | 61 | #### 总结 62 | 63 | 一般的规则是,如果我们不确定返回值类型,我们应该显示声明它。这是很重要的信息,我们不应该把它隐藏起来 (*Item 14: Specify the variable type when it is not clear*)。此外,为了安全起见,在外部API中,我们应该始终指定类型。不能让它们随意改变。当我们的项目迭代时,推断类型可能会有很多限制或者很容易被更改。 64 | -------------------------------------------------------------------------------- /Part 1 Good code/Chapter 1 Safety/Item 6 Prefer standard errors to custom ones.md: -------------------------------------------------------------------------------- 1 | ## 第6条:尽可能使用标准库中提供的异常 2 | 3 | `require`, `check` 和`assert`函数涵盖了Kotlin中最常见的异常情况, 但还是有很多其他情况需要我们去主动抛出异常。例如,当你实现一个库来解析JSON时,当提供的JSON文件格式不正确时时,抛出一个`JsonParsingException`是合理的: 4 | 5 | ``` kotlin 6 | inline fun String.readObject(): T { 7 | //... 8 | if (incorrectSign) { 9 | throw JsonParsingException() 10 | } 11 | //... 12 | return result 13 | } 14 | ``` 15 | 16 | 这里我们使用了一个自定义异常,因为在标准库中没有合适的异常来表答这种情况。你应该尽可能使用标准库提供的异常,而不是定义自己定义的异常。这些标准库提供的异常应该被开发者熟知和复用。在特定场景下使用这些异常会使你的API更容易学习和理解。下面是一些你可以使用的最常见的异常: 17 | 18 | - `IllegalArgumentException` 和`IllegalStateException` - 像在第5条中提到的那样,使用 `require` 和`check` 来抛出该异常。 19 | - `IndexOutOfBoundsException` - 表示索引越界。 常用于集合和数组。比如在 `ArrayList.get(Int)`中就会抛出该异常。 20 | - `ConcurrentModificationException` - 表示并发修改是禁止的,当检测到这种行为时会抛出该异常。 21 | - `UnsupportedOperationException` - 表示该对象不支持它声明的方法。我们应该避免这种情况,当一个方法不受支持时,它就不应该被声明在类中。 22 | - `NoSuchElementException` - 表示被请求的元素不存在。 例如,当我们实现`Iterable`时,迭代器内已经没有其他元素了但是还是调用了 `next` 方法。 -------------------------------------------------------------------------------- /Part 1 Good code/Chapter 1 Safety/Item 7 Prefer null or Failure result when the lack of result is possible.md: -------------------------------------------------------------------------------- 1 | ## 第7条:当不能返回预期结果时,优先使用`null` o或`Failure` 作为返回值 2 | 3 | 有时,函数不能返回预期的结果。常见的例子有: 4 | 5 | - 我们试图从服务器获取数据,但我们的网络连接有问题 6 | - 我们试图获取符合某些条件的第一个元素,但在我们的集合中没有这样的元素 7 | - 我们试图把文本解析为一个对象,但是文本的格式是错误的 8 | 9 | 有两种主要的办法来处理这种情况: 10 | 11 | - 返回一个`null`或一个表示失败的密封类(通常命名为 `Failure`) 12 | - 抛出一个异常 13 | 14 | 这两者之间有一个重要的区别。异常不应该被用作传递信息的标准方式。**所有异常都是指不正确的、特殊的情况,应按这种方式处理。我们应该只在特殊情况下使用异常**(来自Joshua Bloch的Effective Java)**。**这样做的主要原因是: 15 | 16 | - 异常传递的方式对大多数程序员来说可读性较差,并且很容易在代码中漏掉。 17 | - 在Kotlin中,所有异常都未经检查。用户不会被强迫甚至鼓励去处理它们。它们通常没有很好地被标记出来。当我们使用API时,它们实际上是不可见的。 18 | - 因为异常是为异常环境设计的,所以JVM的实现者们没有任何动机让它们有和正常情况一样的执行速度。 19 | - 将代码放在try-catch块中会抑制编译器可能执行的某些优化。 20 | 21 | 另一方面,`null`或`Failure`都是表示预期错误的最佳选择。它们是显式的、高效的,并且可以用惯用的方式处理。这就是为什么**当发生预期内的错误时,我们应该更倾向于返回`null`或`Failure `时,而当发生预期外的错误时,应该更倾向于抛出一个异常。**以下是一些例子: 22 | 23 | ``` kotlin 24 | inline fun String.readObjectOrNull(): T? { 25 | //... 26 | if (incorrectSign) { 27 | return null 28 | } 29 | //... 30 | return result 31 | } 32 | 33 | inline fun String.readObject(): Result { 34 | //... 35 | if (incorrectSign) { 36 | return Failure(JsonParsingException()) 37 | } 38 | //... 39 | return Success(result) 40 | } 41 | 42 | sealed class Result 43 | class Success(val result: T) : Result() 44 | class Failure(val throwable: Throwable) : Result() 45 | 46 | class JsonParsingException : Exception() 47 | ``` 48 | 49 | 当我们选择返回`null`时,我们可以从各种支持空安全的特性中选择来处理这样的值的方式,比如安全调用或使用Elvis操作符: 50 | 51 | ``` kotlin 52 | val age = userText.readObjectOrNull()?.age ?: -1 53 | ``` 54 | 55 | 当我们选择返回像`Result`这样的复合类型时,用户将能够使用When表达式来处理它: 56 | 57 | ``` kotlin 58 | val personResult = userText.readObject() 59 | val age = when(personResult) { 60 | is Success -> personResult.value.age 61 | is Failure -> -1 62 | } 63 | ``` 64 | 65 | 使用这种错误处理不仅比try-catch块更有效,而且通常更容易使用,可读性更好。一个异常可能会被错过,并可能导致整个应用程序停止。虽然`null`值或密封的结果类需要显式处理,但它不会中断应用程序的流程。 66 | 67 | 当在`null`和`Failure`两者间进行选择时,如果失败时需要传递额外的信息,我们应该选择后者,否则应该选择`null`。`Failure`可以保存你需要的任何数据。 68 | 69 | 通常来说我们应该对一个函数提供两种变体——一种会抛出异常,另一种不会。一个很好的例子是`List`有以下两个方法: 70 | 71 | - `get` 返回给定下标位置的元素,如果对应的下标超出了列表范围, 这个方法会抛出`IndexOutOfBoundsException`. 72 | - `getOrNull`,当我们知道我们可能会访问一个超出列表范围的元素时,我们应该使用它,这样当下标超出范围时,它会返回给我们 `null`. 73 | 74 | 它还支持其他选项,如`getOrDefault`,这在某些情况下很有用,但通常可能很容易替换为`getOrNull`和Elvis操作符`?:`。 75 | 76 | 这是一个很好的实践,因为如果开发人员知道他们正在安全地获取一个元素,就不应该强迫他们处理一个可为空的值,同时,如果他们有任何不确定,他们应该使用`getOrNull `并正确地处理默认值。 -------------------------------------------------------------------------------- /Part 1 Good code/Chapter 1 Safety/Item 9 Close resources with use.md: -------------------------------------------------------------------------------- 1 | ## 第9条:使用 `use`关闭资源 2 | 3 | 有些资源不能自动关闭,当我们不再使用它们时,我们需要调用`close`方法来手动关闭。我们在Kotlin/JVM中使用的Java标准库包含了很多这样的资源,例如 4 | 5 | - `InputStream` 和`OutputStream` 6 | - `java.sql.Connection`, 7 | - `java.io.Reader` (`FileReader`, `BufferedReader`, `CSSParser`) 8 | - `java.new.Socket` 和`java.util.Scanner` 9 | 10 | 所有这些资源都实现了`Closeable `接口,它继承自`AutoCloseable `。 11 | 12 | 对于上面列举的这些例子,当我们确认不再需要该资源的时候,我们需要调用`close`方法,因为这些资源的调用开销比较大并且它们被自动关闭的成本也较高(如果没有任何对该资源的引用,垃圾收集器最终会将它关闭,但这一过程所需的时间会比较久)。 因此,为了确保我们不会漏掉关闭它们,我们通常将这些资源调用放在在一个`try-finally`块中,并在`finally`中调用`close`方法: 13 | 14 | ``` kotlin 15 | fun countCharactersInFile(path: String): Int { 16 | val reader = BufferedReader(FileReader(path)) 17 | try { 18 | return reader.lineSequence().sumBy { it.length } 19 | } finally { 20 | reader.close() 21 | } 22 | } 23 | ``` 24 | 25 | 这样的结构是复杂且不正确的。它的不正确体现在`close`可能会抛出异常且这个异常不会被捕获。此外,如果我们同时从`try`和`finally`块中抛出异常,那么只有一个异常会被正确地传递。 我们所期望的表现应该是后抛出的异常信息应该被添加到之前已经抛出的异常信息中。正确的实现很长并且很复杂,但这种处理很常见,因此Kotlin标准库中提供了`use`函数。应该使用它来正确关闭资源和处理异常,此函数可用于任何`Closeable `对象: 26 | 27 | ``` kotlin 28 | fun countCharactersInFile(path: String): Int { 29 | val reader = BufferedReader(FileReader(path)) 30 | reader.use { 31 | return reader.lineSequence().sumBy { it.length } 32 | } 33 | } 34 | ``` 35 | 36 | 调用`use`的对象(本例中为`reader`)也会作为参数传递给lambda表达式,因此语法可以缩短: 37 | 38 | ``` kotlin 39 | fun countCharactersInFile(path: String): Int { 40 | BufferedReader(FileReader(path)).use { reader -> 41 | return reader.lineSequence().sumBy { it.length } 42 | } 43 | } 44 | ``` 45 | 46 | 因为`use`函数经常被用来操作文件,同时逐行读取文件也是一种很常见的操作,所以Kotlin标准库中提供了一个` useLines`函数,它会返回给我们一个包含了文件中每一行内容(`String`类型)的序列,并且在读取完毕之后会自动关闭文件资源: 47 | 48 | ``` kotlin 49 | fun countCharactersInFile(path: String): Int { 50 | File(path).useLines { lines -> 51 | return lines.sumBy { it.length } 52 | } 53 | } 54 | ``` 55 | 56 | 这是一种适合用来处理大文件的方法,因为序列会按需读取每一行,因此每次调用对于内存的占用不会超过一行的内容所对应的内存大小。 但代价是这个序列只能使用一次,如果你需要多次遍历文件,则需要多次调用它。 `useLines` 函数同样也能作为一个表达式来调用。 57 | 58 | ``` kotlin 59 | fun countCharactersInFile(path: String): Int = 60 | File(path).useLines { lines -> 61 | lines.sumBy { it.length } 62 | } 63 | ``` 64 | 65 | 以上所有使用序列对文件进行操作的例子,都是比较合理的处理方法。因为这样我们可以每次只加载一行的内容,避免直接加载整个文件。更多相关内容请参考 *Item 49: Prefer Sequence for big collections with more than one processing step*. 66 | 67 | ### Summary 68 | 69 | 使用`use`对实现了`Closeable`或`AutoCloseable`的对象进行操作,是一个安全且简单的选择。当你需要操作一个文件时,考虑使用`useLines`,它会生成一个序列来帮助你遍历每一行。 -------------------------------------------------------------------------------- /Part 1 Good code/Chapter 2 Readability/Introduction.md: -------------------------------------------------------------------------------- 1 | # 第2章:可读性 2 | 3 | > "任何傻瓜都能编写计算机能理解的代码。优秀的程序员编写的是人类能理解的代码。" 4 | > –马丁·福勒,《重构:改善现有代码的设计》,第15页 5 | 6 | 有一个非常常见的误解,认为Kotlin的设计目标是简洁。实际上,并非如此。有些语言比Kotlin更简洁。例如,我所了解的最简洁的语言是APL。以下是用APL实现的约翰·康威的“生命游戏”(Game of Life): 7 | 8 | ![](../../assets/chapter2/chapter2-1.png) 9 | 10 | 你可能会惊叹于这段代码的简洁。然而,你可能会意识到你的键盘上没有其中一些字符。还有更多这样的语言,例如,下面是同样的程序用J语言实现的版本: 11 | 12 | 13 | 14 | ``` 15 | 1 life=:[:+/(3 4=/[:+/(,/,"0/~i:1)|.])*.1,:] 16 | ``` 17 | 18 | 这两种语言确实非常简洁。这使它们在代码高尔夫比赛中成为冠军。但这也使它们难以阅读。说实话,即使对于经验丰富的APL开发人员(全世界可能只有几个),理解这个程序的功能和工作原理也是一项挑战。 19 | 20 | Kotlin从来没有追求过极致的简洁性。它的设计目标是**可读性**。与其他流行语言相比,Kotlin确实更加简洁,但这是因为Kotlin消除了许多冗余代码和重复结构。这样做是为了帮助开发人员专注于重要的内容,从而使Kotlin更易读。 21 | 22 | Kotlin允许程序员设计干净且有意义的代码和API。其特性使我们能够隐藏或突出显示我们想要的内容。本章将介绍如何明智地使用这些工具。本章作为引言,提供了一些通用建议。虽然它也介绍了可读性的概念,我们将在本书的其余部分中继续涉及这个概念,尤其是在“第2部分:抽象设计”中,我们将深入探讨与类和函数设计相关的主题。 23 | 24 | 让我们从更抽象的关于可读性的主题开始,引入一般性的问题。 25 | -------------------------------------------------------------------------------- /Part 1 Good code/Chapter 2 Readability/Item 11 Design for readability.md: -------------------------------------------------------------------------------- 1 | ## 第11条:可读性设计 2 | 3 | 在编程中,一个已知的观察结果是,开发人员阅读代码的时间远远超过编写代码的时间。一般估计是,写一分钟的代码需要阅读十分钟(这个比例在罗伯特·C·马丁的书《代码整洁之道》中得到普及)。如果你觉得这难以置信,想想你在查找错误时花费了多少时间来阅读代码。我相信每个人在自己的职业生涯中至少有一次遇到过这种情况,他们花了几个星期的时间寻找错误,最终只需改变一行代码就能修复它。当我们学习如何使用新的API时,通常是通过阅读代码来实现的。我们通常通过阅读代码来理解逻辑或实现的方式。**编程主要是关于阅读,而不是写作。**因此,我们应该以可读性为重点进行编码。 4 | 5 | ### 减少认知负荷 6 | 7 | 可读性对每个人来说意味着不同的事情。然而,有一些规则是基于经验形成的,或者来自认知科学。只需比较以下两种实现: 8 | 9 | ``` kotlin 10 | // Implementation A 11 | if (person != null && person.isAdult) { 12 | view.showPerson(person) 13 | } else { 14 | view.showError() 15 | } 16 | 17 | // Implementation B 18 | person?.takeIf { it.isAdult } 19 | ?.let(view::showPerson) 20 | ?: view.showError() 21 | ``` 22 | 23 | 哪个更好,A还是B?使用简单的推理,认为行数较少的更好,这不是一个好答案。我们可以从第一个实现中删除换行符,它不会变得更易读。 24 | 25 | 这两种结构的可读性取决于我们能多快地理解它们。而这又在很大程度上取决于我们的大脑对每个习惯用法(结构、函数、模式)的训练程度。对于Kotlin的初学者来说,显然实现A更易读。它使用了常见的习惯用法(if/else,`&&`,方法调用)。实现B使用了Kotlin特有的习惯用法(安全调用`?.`,`takeIf`,`let`,Elvis运算符`?:`,有界函数引用`view::showPerson`)。当然,所有这些习惯用法在Kotlin中广泛使用,所以大多数有经验的Kotlin开发人员都熟悉它们。然而,很难进行比较。Kotlin并不是大多数开发人员的第一门语言,我们在一般编程方面拥有更多的经验而不是Kotlin编程方面的经验。我们编写的代码不仅是为有经验的开发人员编写的。你雇佣的初级开发人员(在经过数月的寻找高级开发人员无果之后)很可能不知道`let`、`takeIf`和有界引用是什么。他们很可能从未见过以这种方式使用Elvis运算符。这个人可能会花一整天来琢磨这个代码块。此外,即使对于有经验的Kotlin开发人员来说,他们也不只使用Kotlin这一种编程语言。让大脑识别Kotlin特定的习惯用法总是需要一些时间。即使在使用Kotlin多年之后,我仍然能够更快地理解第一个实现。每个不太常见的习惯用法都会引入一些复杂性,当我们在需要几乎同时理解所有这些习惯用法的单个语言块时,这种复杂性会累积起来。 26 | 27 | 请注意,实现方式A更容易修改。假设我们需要在if块中添加额外的操作。在实现方式A中,这样的添加很容易。在实现方式B中,我们不能再使用函数引用。在实现方式B的else块中添加内容更加困难 - 我们需要使用某个函数来能够在Elvis运算符的右侧容纳多个表达式。 28 | 29 | ``` kotlin 30 | if (person != null && person.isAdult) { 31 | view.showPerson(person) 32 | view.hideProgressWithSuccess() 33 | } else { 34 | view.showError() 35 | view.hideProgress() 36 | } 37 | 38 | person?.takeIf { it.isAdult } 39 | ?.let { 40 | view.showPerson(it) 41 | view.hideProgressWithSuccess() 42 | } ?: run { 43 | view.showError() 44 | view.hideProgress() 45 | } 46 | ``` 47 | 48 | 调试实现方式A也要简单得多。难怪如此 - 调试工具是为这种基本结构而设计的。 49 | 50 | 一般规则是,不太常见和"创造性"的结构通常不太灵活,支持也不那么好。举个例子,假设我们需要添加第三个分支,在person为null时显示不同的错误,当person不是成年人时显示另一种错误。在实现方式A中,我们可以轻松地将if/else更改为when,使用IntelliJ的重构功能,然后轻松地添加额外的分支。而在实现方式B中,同样的更改将非常困难,可能需要完全重写。 51 | 52 | 你有没有注意到实现方式A和B甚至没有以相同的方式工作?你能看出区别吗?现在回过头来思考一下。 53 | 54 | 区别在于let从lambda表达式中返回一个结果。这意味着如果showPerson返回null,那么第二个实现方式也会调用showError!这显然不明显,它告诉我们,当我们使用不太熟悉的结构时,更容易遇到意外行为(并且很难发现)。 55 | 56 | 这里的一般规则是我们要减少认知负荷。我们的大脑识别模式,并基于这些模式构建我们对程序如何工作的理解。当我们考虑可读性时,我们希望缩短这个距离。我们更喜欢更少的代码,但也更常见的结构。当我们经常看到熟悉的模式时,我们会识别出它们。我们总是更喜欢在其他学科中熟悉的结构。 57 | 58 | ### 不要过于极端 59 | 60 | 仅仅因为在之前的例子中我演示了`let`的误用,并不意味着它应该被完全避免。`let`是一种常见的习惯用法,在各种情境下合理使用可以改善代码。一个常见的例子是当我们有一个可为空的可变属性,并且只有在它不为null时才执行某个操作。由于可变属性可能会被其他线程修改,因此无法使用智能转换。解决这个问题的一种很好的方法是使用安全调用`let`: 61 | 62 | ``` kotlin 63 | class Person(val name: String) 64 | var person: Person? = null 65 | 66 | fun printName() { 67 | person?.let { 68 | print(it.name) 69 | } 70 | } 71 | ``` 72 | 73 | 这样的习惯用法是广为人知且使用广泛的。`let`还有很多其他合理的用例。例如: 74 | 75 | - 将操作移到其参数计算之后 76 | - 使用`let`将对象包装为装饰器 77 | 78 | 以下是展示这两种用例的示例(还使用了函数引用): 79 | 80 | ``` kotlin 81 | students 82 | .filter { it.result >= 50 } 83 | .joinToString(separator = "\n") { 84 | "${it.name} ${it.surname}, ${it.result}" 85 | } 86 | .let(::print) 87 | 88 | var obj = FileInputStream("/file.gz") 89 | .let(::BufferedInputStream) 90 | .let(::ZipInputStream) 91 | .let(::ObjectInputStream) 92 | .readObject() as SomeObject 93 | ``` 94 | 95 | 在所有这些情况下,我们付出了一定的代价 - 这段代码更难调试,对于经验较少的Kotlin开发人员来说理解起来也更困难。但我们为此付出了代价,而且似乎是一个公平的交易。问题在于当我们为没有充分理由引入大量复杂性时。 96 | 97 | 对于某些事物是否合理将始终存在争议。在这方面取得平衡是一门艺术。然而,认识到不同的结构引入了复杂性或者使事情更加清晰是很重要的。尤其是当它们一起使用时,两种结构的复杂性通常远远超过它们各自复杂性的总和。 98 | 99 | ### 约定俗成 100 | 101 | 我们承认不同的人对可读性有不同的看法。我们经常在函数命名上争论不休,讨论什么应该是显式的,什么是隐式的,应该使用什么习语等等。编程是一门表达能力的艺术。然而,也有一些需要理解和记住的约定俗成。 102 | 103 | 当我在旧金山的一个研讨会上的一个小组问我在Kotlin中最糟糕的事情是什么时,我给了他们这个例子: 104 | 105 | ``` kotlin 106 | val abc = "A" { "B" } and "C" 107 | print(abc) // ABC 108 | ``` 109 | 110 | 我们只需要以下代码就能实现这种可怕的语法: 111 | 112 | ``` kotlin 113 | operator fun String.invoke(f: ()->String): String = 114 | this + f() 115 | 116 | infix fun String.and(s: String) = this + s 117 | ``` 118 | 119 | 这段代码违反了我们之后将要描述的许多规则: 120 | 121 | - 它违反了操作符的含义 - `invoke`不应该以这种方式使用。一个字符串不能被调用。 122 | - 在这里使用“lambda作为最后一个参数”的约定会令人困惑。在函数之后使用它是可以的,但是当我们在invoke操作符上使用它时要非常小心。 123 | - `and`显然是这个中缀方法的一个糟糕的名称。`append`或`plus`会好得多。 124 | - 我们已经有了用于字符串连接的语言特性,应该使用它们而不是重复造轮子。 125 | 126 | 127 | 在每个建议背后,都有一条更普遍的规则来保护良好的Kotlin风格。在本章中,我们将介绍最重要的规则,从重写操作符开始讨论。 -------------------------------------------------------------------------------- /Part 1 Good code/Chapter 2 Readability/Item 12 Operator meaning should be consistent with its function name.md: -------------------------------------------------------------------------------- 1 | ## 第12条:操作符的含义应与其函数名一致 2 | 3 | 操作符重载是一项功能强大的特性,但与大多数强大功能一样,它也是危险的。在编程中,伴随强大的能力而来的是巨大的责任。作为一名培训师,我经常看到人们在刚刚发现操作符重载时会过度使用。例如,一个练习涉及创建一个计算阶乘的函数: 4 | 5 | ``` kotlin 6 | fun Int.factorial(): Int = (1..this).product() 7 | 8 | fun Iterable.product(): Int = 9 | fold(1) { acc, i -> acc * i } 10 | ``` 11 | 12 | 由于这个函数被定义为`Int`的扩展函数,使用起来非常方便: 13 | 14 | ``` kotlin 15 | print(10 * 6.factorial()) // 7200 16 | ``` 17 | 18 | 数学家知道,阶乘有一个特殊的表示法,即在数字后面加一个感叹号: 19 | 20 | ``` kotlin 21 | 10 * 6! 22 | ``` 23 | 24 | 在Kotlin中并没有支持这样的操作符,但是正如我的一个研讨会参与者注意到的那样,我们可以使用`not`进行操作符重载: 25 | 26 | ``` kotlin 27 | operator fun Int.not() = factorial() 28 | 29 | print(10 * !6) // 7200 30 | ``` 31 | 32 | 虽然我们可以这样做,但我们是否应该这样做呢?最简单的答案是不应该。只需要读取函数的声明就可以注意到,这个函数的名称是`not`。正如这个名称所暗示的,它不应该被这样使用。它表示的是逻辑操作,而不是数值阶乘。这种用法会导致混乱和误导。在Kotlin中,所有操作符只是具有具体名称的函数的语法糖。下表展示了每个操作符在Kotlin中对应的具体名称。每个操作符都可以以函数的形式调用,而不是使用操作符的语法。下面的代码会是什么样子呢? 33 | 34 | ``` kotlin 35 | print(10 * 6.not()) // 7200 36 | ``` 37 | 38 | ![What each operator translates to in Kotlin.](../../assets/chapter2/chapter2-2.png) 39 | 40 | 在Kotlin中,每个操作符的含义始终保持不变。这是一个非常重要的设计决策。有些语言,比如Scala,允许您无限制地进行操作符重载。这种自由度被一些开发人员滥用的现象广为人知。即使函数和类的名称有意义,第一次阅读使用不熟悉的库的代码可能也很困难。现在想象一下,操作符被用于另一种意义,只有熟悉*范畴论*的开发人员才知道。理解起来将更加困难。您需要分别理解每个操作符,在特定上下文中记住它的含义,然后将所有这些都记住以连接各个部分来理解整个语句。在Kotlin中,我们没有这样的问题,因为每个操作符都有一个具体的含义。例如,当您看到以下表达式时: 41 | 42 | ``` kotlin 43 | x + y == z 44 | ``` 45 | 46 | 您知道这等同于: 47 | 48 | ``` kotlin 49 | x.plus(y).equal(z) 50 | ``` 51 | 52 | 或者如果`plus`声明了可为空的返回类型,可以是以下代码: 53 | 54 | ``` kotlin 55 | (x.plus(y))?.equal(z) ?: (z === null) 56 | ``` 57 | 58 | 这些都是具有具体名称的函数,我们期望所有函数都按照它们的名称所指示的方式工作。这严格限制了每个操作符可以用于什么目的。使用`not`来返回`factorial`是对这个约定的明显违反,不应该发生。 59 | 60 | ### 不清晰的情况 61 | 62 | 最大的问题是在某些情况下不清楚某种用法是否符合约定。例如,当我们将一个函数三倍化时,这意味着什么?对于某些人来说,意思是创建另一个重复该函数三次的函数: 63 | 64 | ``` kotlin 65 | operator fun Int.times(operation: () -> Unit): ()->Unit = 66 | { repeat(this) { operation() } } 67 | 68 | val tripledHello = 3 * { print("Hello") } 69 | 70 | tripledHello() // Prints: HelloHelloHello 71 | ``` 72 | 73 | 对于其他人来说,意思是我们要调用该函数三次: 74 | 75 | ``` kotlin 76 | operator fun Int.times(operation: ()->Unit) { 77 | repeat(this) { operation() } 78 | } 79 | 80 | 3 * { print("Hello") } // Prints: HelloHelloHello 81 | ``` 82 | 83 | 当意义不清楚时,最好使用描述性的扩展函数。如果我们希望保持类似操作符的语法,可以使用`infix`修饰符或顶层函数: 84 | 85 | ``` kotlin 86 | infix fun Int.timesRepeated(operation: ()->Unit) = { 87 | repeat(this) { operation() } 88 | } 89 | 90 | val tripledHello = 3 timesRepeated { print("Hello") } 91 | tripledHello() // Prints: HelloHelloHello 92 | ``` 93 | 94 | 有时候最好使用一个顶层函数。重复调用函数三次的功能已经在标准库中实现: 95 | 96 | ``` kotlin 97 | repeat(3) { print("Hello") } // Prints: HelloHelloHello 98 | ``` 99 | 100 | ### 什么时候可以打破这个规则? 101 | 102 | 有一个非常重要的情况可以打破这个规则:当我们设计领域特定语言(DSL)时。想象一个经典的HTML DSL示例: 103 | 104 | ``` kotlin 105 | body { 106 | div { 107 | +"Some text" 108 | } 109 | } 110 | ``` 111 | 112 | 您可以看到,为了将文本添加到元素中,我们使用了`String.unaryPlus`。这是可以接受的,因为它显然是领域特定语言(DSL)的一部分。在这个特定的上下文中,读者并不会对使用不同规则感到惊讶。 113 | 114 | ### 总结 115 | 116 | 要谨慎使用操作符重载。函数名称应与其行为一致。避免操作符含义不清楚的情况。通过使用具有描述性名称的常规函数来澄清。如果希望具有更类似操作符的语法,则可以使用`infix`修饰符或顶层函数。 -------------------------------------------------------------------------------- /Part 1 Good code/Chapter 2 Readability/Item 13 Avoid returning or operating on Unit?.md: -------------------------------------------------------------------------------- 1 | ## 第13条:避免返回或操作 `Unit?` 2 | 3 | 在招聘过程中,我的一位亲密朋友被问到:“为什么有人希望从函数返回 `Unit?`?”嗯,`Unit?` 只有两个可能的值:`Unit` 或 `null`。就像 `Boolean` 可以是 `true` 或 `false` 一样。因此,这些类型是同构的,因此它们可以互换使用。为什么我们要使用 `Unit?` 而不是 `Boolean` 来表示某个东西呢?我没有其他答案,除了可以使用 Elvis 操作符或安全调用。所以,我们可以这样做: 4 | 5 | ```kotlin 6 | fun keyIsCorrect(key: String): Boolean = //... 7 | 8 | if(!keyIsCorrect(key)) return 9 | ``` 10 | 11 | 我们也可以这样做: 12 | 13 | ```kotlin 14 | fun verifyKey(key: String): Unit? = //... 15 | 16 | verifyKey(key) ?: return 17 | ``` 18 | 19 | 这似乎是预期的答案。但是,我朋友面试中缺少一个更重要的问题:“我们应该这样做吗?”虽然这种技巧在编写代码时看起来不错,但在阅读代码时可能并非如此。使用 `Unit?` 来表示逻辑值是具有误导性的,可能会导致难以检测的错误。我们已经讨论过这种表达式可能会带来意外结果的情况: 20 | 21 | ```kotlin 22 | getData()?.let{ view.showData(it) } ?: view.showError() 23 | ``` 24 | 25 | 当 `showData` 返回 `null` 而 `getData` 返回非 `null` 时,`showData` 和 `showError` 都将被调用。使用标准的 if-else 结构可以更少出错且更易读: 26 | 27 | ```kotlin 28 | if (person != null && person.isAdult) { 29 | view.showPerson(person) 30 | } else { 31 | view.showError() 32 | } 33 | ``` 34 | 35 | 比较以下两种写法: 36 | 37 | ```kotlin 38 | if(!keyIsCorrect(key)) return 39 | 40 | verifyKey(key) ?: return 41 | ``` 42 | 43 | 我从未找到过任何情况下 `Unit?` 是最可读选项的案例。它具有误导性和困惑性。几乎总是应该用 `Boolean` 来替代它。这就是为什么通常规定应避免返回或操作 `Unit?` 的一般原则。 -------------------------------------------------------------------------------- /Part 1 Good code/Chapter 2 Readability/Item 14 Specify the variable type when it is not clear.md: -------------------------------------------------------------------------------- 1 | ## 第14条:在类型不明确的情况下,请显式声明变量的类型 2 | 3 | Kotlin拥有一个很好的类型推断系统,使我们能够在类型对开发者来说是显而易见的情况下省略类型声明: 4 | 5 | ```kotlin 6 | val num = 10 7 | val name = "Marcin" 8 | val ids = listOf(12, 112, 554, 997) 9 | ``` 10 | 11 | 这不仅提高了开发时间,而且在类型从上下文中明确时,额外的指定会显得冗余和混乱。然而,在类型不清楚的情况下,不应滥用这种方式: 12 | 13 | ```kotlin 14 | val data = getSomeData() 15 | ``` 16 | 17 | 我们为了可读性而设计我们的代码,不应隐藏对读者可能很重要的重要信息。不能以返回类型可以省略为理由,因为读者始终可以跳转到函数的规范中查看。类型也可能在那里被推断,用户可能会陷入越来越深的细节。此外,用户可能在GitHub或其他不支持跳转到实现的环境中阅读此代码。即使他们可以,我们都有非常有限的工作记忆,像这样浪费它并不是一个好主意。类型是重要的信息,如果不清楚,应该明确指定。 18 | 19 | ```kotlin 20 | val data: UserData = getSomeData() 21 | ``` 22 | 23 | 提高可读性并不是类型规定的唯一场景。它还涉及到安全性,就像在 *Chapter: Safety* 的 *Item 3: Eliminate platform types as soon as possible* 和 *Item 4: Do not expose inferred types* 中所示。**类型对开发者和编译器都是重要的信息。每当它是重要的信息时,请毫不犹豫地指定类型。它的代价很小,但可以帮助很多。** -------------------------------------------------------------------------------- /Part 1 Good code/Chapter 2 Readability/Item 16 Properties should represent state not behavior.md: -------------------------------------------------------------------------------- 1 | ## 第16条:属性应代表状态,而不是行为 2 | 3 | Kotlin的属性看起来类似于Java的字段,但它们实际上代表了一个不同的概念。 4 | 5 | ``` kotlin 6 | // Kotlin属性 7 | var name: String? = null 8 | 9 | // Java字段 10 | String name = null; 11 | ``` 12 | 13 | 尽管它们可以以相同的方式使用,来存储数据,我们需要记住,属性具有更多的功能。首先,它们总是可以有自定义的设置器和获取器: 14 | 15 | ``` kotlin 16 | var name: String? = null 17 | get() = field?.toUpperCase() 18 | set(value) { 19 | if(!value.isNullOrBlank()) { 20 | field = value 21 | } 22 | } 23 | ``` 24 | 25 | 你可以看到我们在这里使用了`field`标识符。这是对让我们在此属性中保存数据的后备字段的引用。这样的后备字段是默认生成的,因为设置器和获取器的默认实现使用它们。我们也可以实现不使用它们的自定义访问器,在这种情况下,属性将根本没有`field`。例如,Kotlin属性可以只使用获取器定义为只读属性`val`: 26 | 27 | ``` kotlin 28 | val fullName: String 29 | get() = "$name $surname" 30 | ``` 31 | 32 | 对于可读写的属性`var`,我们可以通过定义获取器和设置器来创建一个属性。这样的属性被称为*派生属性*,并且并不少见。它们是Kotlin中所有属性默认被封装的主要原因。只需想象一下,你需要在你的对象中保存一个日期,你使用了Java stdlib的`Date`。然后在某个时候,由于某种原因,对象无法再存储这种类型的属性。可能是因为序列化问题,或者可能是因为你将此对象提升到了一个公共模块。问题在于,这个属性已经在你的项目中被引用。有了Kotlin,这就不再是问题,因为你可以将你的数据移动到一个单独的属性`millis`,并修改`date`属性,使其不再保存数据,而是包装/解包那个其他属性。 33 | 34 | ``` kotlin 35 | var date: Date 36 | get() = Date(millis) 37 | set(value) { 38 | millis = value.time 39 | } 40 | ``` 41 | 42 | 属性不需要字段。从概念上讲,它们代表访问器(`val`的 getter,`var`的 getter 和 setter)。这就是我们可以在接口中定义它们的原因: 43 | 44 | ``` kotlin 45 | interface Person { 46 | val name: String 47 | } 48 | ``` 49 | 50 | 这意味着这个接口承诺有一个 getter。我们也可以覆盖属性: 51 | 52 | ``` kotlin 53 | open class Supercomputer { 54 | open val theAnswer: Long = 42 55 | } 56 | 57 | class AppleComputer : Supercomputer() { 58 | override val theAnswer: Long = 1_800_275_2273 59 | } 60 | ``` 61 | 62 | 出于同样的原因,我们可以委托属性: 63 | 64 | ``` kotlin 65 | val db: Database by lazy { connectToDb() } 66 | ``` 67 | 68 | 属性委托在第21条:使用属性委托提取常见的属性模式中详细描述。因为属性本质上是函数,我们也可以创建扩展属性: 69 | 70 | ``` kotlin 71 | val Context.preferences: SharedPreferences 72 | get() = PreferenceManager 73 | .getDefaultSharedPreferences(this) 74 | 75 | val Context.inflater: LayoutInflater 76 | get() = getSystemService( 77 | Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater 78 | 79 | val Context.notificationManager: NotificationManager 80 | get() = getSystemService(Context.NOTIFICATION_SERVICE) 81 | as NotificationManager 82 | ``` 83 | 84 | 如你所见,**属性代表访问器,而不是字段**。这样,它们可以代替一些函数,但我们应该小心我们用它们来做什么。属性不应该用来表示像下面的例子中的算法行为: 85 | 86 | ``` kotlin 87 | // DON’T DO THIS! 88 | val Tree.sum: Int 89 | get() = when (this) { 90 | is Leaf -> value 91 | is Node -> left.sum + right.sum 92 | } 93 | ``` 94 | 95 | "这里的 `sum` 属性遍历所有元素,因此它代表了算法行为。因此,这个属性具有误导性:对于大型集合,寻找答案可能在计算上很重,这完全出乎预料。这不应该是一个属性,而应该是一个函数: 96 | 97 | ``` kotlin 98 | fun Tree.sum(): Int = when (this) { 99 | is Leaf -> value 100 | is Node -> left.sum() + right.sum() 101 | } 102 | ``` 103 | 104 | 一般规则是,**我们只应该用它们来表示或设置状态,不应该涉及其他逻辑**。一个有用的启发式规则来决定是否应该是一个属性是:如果我将这个属性定义为一个函数,我会在它前面加上get/set吗?如果不是,那么它可能不应该是一个属性。更具体地说,以下是我们不应该使用属性,而应该使用函数的最典型情况: 105 | 106 | - **操作在计算上昂贵或计算复杂度高于O(1)** - 用户不期望使用属性可能会很昂贵。如果是这样,使用函数更好,因为它传达了可能会这样,用户可能会节俭地使用它,或者开发者可能会考虑缓存它。 107 | 108 | - **它涉及业务逻辑(应用程序如何行动)** - 当我们阅读代码时,我们不期望一个属性可能会做任何超过简单动作的事情,比如记录,通知监听器,或者更新一个绑定的元素。 109 | 110 | - **它不是确定性的** - 连续两次调用成员会产生不同的结果。 111 | 112 | - **它是一个转换,比如** `Int.toDouble()` - 按照惯例,转换是一个方法或一个扩展函数。使用属性会像引用某个内部部分,而不是包装整个对象。 113 | 114 | - **获取器不应改变属性状态** - 我们期望我们可以自由地使用获取器,而不用担心属性状态的修改。 115 | 116 | 117 | 118 | 例如,计算元素的总和需要遍历所有元素(这是行为,而不是状态),并且具有线性复杂性。因此,它不应该是一个属性,并且在标准库中被定义为一个函数: 119 | 120 | ``` kotlin 121 | val s = (1..100).sum() 122 | ``` 123 | 124 | 另一方面,为了获取和设置状态,我们在Kotlin中使用属性,除非有充分的理由,否则我们不应该涉及到函数。我们使用属性来表示和设置状态,如果你以后需要修改它们,使用自定义的获取器和设置器: 125 | 126 | ``` kotlin 127 | // DON’T DO THIS! 128 | class UserIncorrect { 129 | private var name: String = "" 130 | 131 | fun getName() = name 132 | 133 | fun setName(name: String) { 134 | this.name = name 135 | } 136 | } 137 | 138 | class UserCorrect { 139 | var name: String = "" 140 | } 141 | ``` 142 | 143 | 一个简单的经验法则是,**属性描述和设置状态,而函数描述行为**。 144 | 145 | -------------------------------------------------------------------------------- /Part 1 Good code/Chapter 2 Readability/Item 17 Consider naming arguments.md: -------------------------------------------------------------------------------- 1 | ## 第17条:考虑命名参数 2 | 3 | 当你阅读代码时,参数的含义并不总是清晰的。看看下面的例子: 4 | 5 | ``` kotlin 6 | val text = (1..10).joinToString("|") 7 | ``` 8 | 9 | `"|"`是什么?如果你对`joinToString`很熟悉,你就知道它是`separator`。虽然它也可能是`prefix`。这一点完全不清楚。我们可以通过明确那些值并不清楚其含义的参数来使其更易读。最好的方法就是使用命名参数: 10 | 11 | ``` kotlin 12 | val text = (1..10).joinToString(separator = "|") 13 | ``` 14 | 15 | 我们也可以通过命名变量达到类似的效果: 16 | 17 | ``` kotlin 18 | val separator = "|" 19 | val text = (1..10).joinToString(separator) 20 | ``` 21 | 22 | 尽管命名参数更可靠。变量名指定了开发者的意图,但并不一定是正确的。如果开发者犯了错误,把变量放在了错误的位置呢?如果参数的顺序改变了呢?命名参数可以保护我们免受这种情况的影响,而命名值则不能。这就是为什么当我们有命名值时,仍然有理由使用命名参数: 23 | 24 | ``` kotlin 25 | val separator = "|" 26 | val text = (1..10).joinToString(separator = separator) 27 | ``` 28 | 29 | ### 我们应该何时使用命名参数? 30 | 31 | 显然,命名参数更长,但它们有两个重要的优点: 32 | 33 | - 名称指示预期的值。 34 | - 它们更安全,因为它们独立于顺序。 35 | 36 | 参数名称不仅对使用此函数的开发者重要,对阅读它如何被使用的人也重要。看看这个调用: 37 | 38 | ``` kotlin 39 | sleep(100) 40 | ``` 41 | 42 | 它会睡多久?100毫秒?可能是100秒?我们可以使用命名参数来澄清它: 43 | 44 | ``` kotlin 45 | sleep(timeMillis = 100) 46 | ``` 47 | 48 | 在这种情况下,这并不是唯一的澄清选项。在像Kotlin这样的静态类型语言中,当我们传递参数时,保护我们的第一个机制是参数类型。我们可以在这里使用它来表达关于时间单位的信息: 49 | 50 | ``` kotlin 51 | sleep(Millis(100)) 52 | ``` 53 | 54 | 或者,我们可以使用扩展属性来创建类似DSL的语法: 55 | 56 | ``` kotlin 57 | sleep(100.ms) 58 | ``` 59 | 60 | 类型是传递此类信息的好方法。如果你关心效率,可以使用*第46条:对具有功能类型参数的函数使用内联修饰符*中描述的内联类。它们帮助我们保证参数的安全性,但并不能解决所有问题。一些参数可能仍然不清楚。一些参数可能仍然被放在错误的位置。这就是为什么我仍然建议考虑使用命名参数,特别是对于参数: 61 | 62 | - 带有默认参数的, 63 | - 与其他参数类型相同的, 64 | - 功能类型的,如果它们不是最后一个参数。 65 | 66 | ### 带有默认参数的参数 67 | 68 | 当一个属性有一个默认参数时,我们几乎总是应该通过名称使用它。这种可选参数比那些必需的参数更常更改。我们不想错过这样的变化。函数名称通常指示其非可选参数是什么,但不是其可选参数是什么。这就是为什么命名可选参数通常更安全、更清洁的原因。 69 | 70 | ### 许多类型相同的参数 71 | 72 | 正如我们所说,当参数有不同的类型时,我们通常可以避免将参数放在错误的位置。但是,当一些参数有相同的类型时,就没有这样的自由了。 73 | 74 | ``` kotlin 75 | fun sendEmail(to: String, message: String) { /*...*/ } 76 | ``` 77 | 78 | 对于这样的函数,最好使用名称来澄清参数: 79 | 80 | ``` kotlin 81 | sendEmail( 82 | to = "contact@kt.academy", 83 | message = "Hello, ..." 84 | ) 85 | ``` 86 | 87 | ### 功能类型的参数 88 | 89 | 最后,我们应该特别对待具有函数类型的参数。在Kotlin中,这样的参数有一个特殊的位置:最后一个位置。有时,函数名描述了一个函数类型的参数。例如,当我们看到`repeat`时,我们期望在其后的lambda是应该被重复的代码块。当你看到`thread`时,直观地认为在其后的块是这个新线程的主体。这样的名称只描述了在最后位置使用的函数。 90 | 91 | ``` kotlin 92 | thread { 93 | // ... 94 | } 95 | ``` 96 | 97 | 所有其他具有函数类型的参数都应该被命名,因为很容易误解它们。例如,看看这个简单的视图DSL: 98 | 99 | ``` kotlin 100 | val view = linearLayout { 101 | text("Click below") 102 | button({ /* 1 */ }, { /* 2 */ }) 103 | } 104 | ``` 105 | 106 | 哪个函数是这个构建器的一部分,哪个是点击监听器?我们应该通过命名监听器并将构建器移出参数来澄清这一点: 107 | 108 | ``` kotlin 109 | val view = linearLayout { 110 | text("Click below") 111 | button(onClick = { /* 1 */ }) { 112 | /* 2 */ 113 | } 114 | } 115 | ``` 116 | 117 | 函数类型的多个可选参数可能会特别令人困惑: 118 | 119 | ``` kotlin 120 | fun call(before: ()->Unit = {}, after: ()->Unit = {}){ 121 | before() 122 | print("Middle") 123 | after() 124 | } 125 | 126 | call({ print("CALL") }) // CALLMiddle 127 | call { print("CALL") } // MiddleCALL 128 | ``` 129 | 130 | 为了防止这种情况,当没有一个具有特殊含义的函数类型的参数时,给它们全部命名: 131 | 132 | ``` kotlin 133 | call(before = { print("CALL") }) // CALLMiddle 134 | call(after = { print("CALL") }) // MiddleCALL 135 | ``` 136 | 137 | 对于反应式库来说,这一点尤其重要。例如,在RxJava中,当我们订阅一个`Observable`时,我们可以设置应该被调用的函数: 138 | 139 | - 在每个接收到的项目上 140 | - 在出现错误的情况下 141 | - 在observable完成后 142 | 143 | 在Java中,我经常看到人们使用lambda表达式来设置它们,并在注释中指定每个lambda表达式是哪个方法。 144 | 145 | ``` kotlin 146 | // Java 147 | observable.getUsers() 148 | .subscribe((List users) -> { // onNext 149 | // ... 150 | }, (Throwable throwable) -> { // onError 151 | // ... 152 | }, () -> { // onCompleted 153 | // ... 154 | }); 155 | ``` 156 | 157 | 在 Kotlin 中,我们可以进一步使用命名参数: 158 | 159 | ``` kotlin 160 | observable.getUsers() 161 | .subscribeBy( 162 | onNext = { users: List -> 163 | // ... 164 | }, 165 | onError = { throwable: Throwable -> 166 | // ... 167 | }, 168 | onCompleted = { 169 | // ... 170 | }) 171 | ``` 172 | 173 | 注意,我将函数名从 `subscribe` 更改为 `subscribeBy`。这是因为 `RxJava` 是用 Java 编写的,**我们在调用 Java 函数时不能使用命名参数**。这是因为 Java 不保留有关函数名称的信息。为了能够使用命名参数,我们通常需要为这些函数制作我们的 Kotlin 包装器(作为这些函数的替代方案的扩展函数)。 174 | 175 | ### 总结 176 | 177 | 命名参数不仅在我们需要跳过一些默认值时有用。它们对于阅读我们代码的开发人员来说是重要的信息,它们可以提高我们代码的安全性。当我们有更多相同类型的参数(或具有功能类型的参数)和可选参数时,我们应该考虑它们。当我们有多个具有功能类型的参数时,它们几乎总是应该被命名。例外情况是最后一个函数参数,当它具有特殊含义,如在 DSL 中。 -------------------------------------------------------------------------------- /Part 1 Good code/Chapter 2 Readability/Item 18 Respect coding conventions.md: -------------------------------------------------------------------------------- 1 | ## 第18条:尊重编码规范 2 | 3 | Kotlin有着在文档中详细描述的成熟编码规范,这部分被恰当地称为“编码规范”。**这些规范并不适用于所有项目,但对我们这个社区来说,最好的方式是在所有项目中都遵守这些规范。**多亏了这些规范: 4 | 5 | - 在项目之间切换更容易 6 | - 代码对外部开发者来说更易读 7 | - 更容易猜测代码如何运行 8 | - 更容易将代码与公共仓库合并,或者将代码的某些部分从一个项目移动到另一个项目 9 | 10 | 程序员应该熟悉文档中描述的这些规范。他们也应该在规范变化时尊重这些规范 - 这可能会在一定程度上随着时间的推移发生。由于这两者都很难做到,所以有两个工具可以帮助: 11 | 12 | - IntelliJ格式化器可以设置为自动按照官方编码规范样式进行格式化。为此,转到设置 | 编辑器 | 代码样式 | Kotlin,点击右上角的“Set from…”链接,并从菜单中选择“预定义样式 / Kotlin样式指南”。 13 | - ktlint - 流行的linter,可以分析你的代码并通知你所有的编码规范违规情况。 14 | 15 | 看看Kotlin项目,我发现大多数项目都与大多数规范直观地一致。这可能是因为Kotlin主要遵循Java编码规范,而大多数现在的Kotlin开发者都是Java开发者。我经常看到被违反的一条规则是类和函数应该如何格式化。根据规范,带有短主构造函数的类可以在一行中定义: 16 | 17 | ``` 18 | class FullName(val name: String, val surname: String) 19 | ``` 20 | 21 | 然而,带有许多参数的类应该格式化,使得每个参数都在另一行,且第一行没有参数: 22 | ``` kotlin 23 | class Person( 24 | val id: Int = 0, 25 | val name: String = "", 26 | val surname: String = "" 27 | ) : Human(id, name) { 28 | // 主体 29 | } 30 | ``` 31 | 32 | 同样,这是我们如何格式化一个长函数的方式: 33 | 34 | ``` kotlin 35 | public fun Iterable.joinToString( 36 | separator: CharSequence = ", ", 37 | prefix: CharSequence = "", 38 | postfix: CharSequence = "", 39 | limit: Int = -1, 40 | truncated: CharSequence = "...", 41 | transform: ((T) -> CharSequence)? = null 42 | ): String { 43 | // ... 44 | } 45 | ``` 46 | 47 | 注意,这两种方式与将第一个参数留在同一行,然后将所有其他参数缩进到它的约定非常不同。 48 | 49 | ``` kotlin 50 | // 不要这样做 51 | class Person(val id: Int = 0, 52 | val name: String = "", 53 | val surname: String = "") : Human(id, name){ 54 | // 主体 55 | } 56 | ``` 57 | 58 | 这可能会有两个问题: 59 | 60 | - 每个类的参数都以类名不同的缩进开始。此外,当我们更改类名时,我们需要调整所有主构造函数参数的缩进。 61 | - 这样定义的类仍然过于宽泛。这种方式定义的类的宽度是类名、`class`关键字和最长的主构造函数参数,或者最后一个参数加上超类和接口。 62 | 63 | 一些团队可能决定使用稍微不同的约定。这没问题,但那么这些约定应该在整个项目中得到尊重。**每个项目看起来都应该像是由一个人编写的,而不是一群人在互相斗争。** 64 | 65 | 编码约定通常不被开发者足够尊重,但它们很重要,而且在最佳实践书籍中关于可读性的章节中,不能没有至少一个简短的部分专门讨论它们。阅读它们,使用静态检查器帮助你与它们保持一致,将它们应用在你的项目中。通过尊重编码约定,我们使Kotlin项目对我们所有人都更好。 -------------------------------------------------------------------------------- /Part 2 Code design/Chapter 3 Reusability/Introduction.md: -------------------------------------------------------------------------------- 1 | # Chapter 3: Reusability 2 | 3 | Have you ever wondered how the `System.out.print` function works (`print` function in Kotlin/JVM)? It’s one of the most basic functions used again and again and yet, would you be able to implement it yourself if it would vanish one day? Truth is that this is not an easy task. Especially if the rest of java.io would vanish as well. You would need to implement the communication with the operating system in C using JNI, separately for each operating system you support[1](chap65.xhtml#fn-footnote_30_note). Believe me, implementing it once is terrifying. Implementing it again and again in every project would be a horror. 4 | 5 | The same applies to many other functions as well. Making Android views is so easy because Android has a complex API that supports it. A backend developer doesn’t need to know too much about the HTTP(S) protocol even though they work with it every day. You don’t need to know any sorting algorithms to call `Iterable.sorted`. Thankfully, we don’t need to have and use all this knowledge every day. Someone implemented it once, and now we can use it whenever we need. This demonstrates a key feature of Programming languages: *reusability*. 6 | 7 | Sounds enthusiastic, but code reusability is as dangerous as it is powerful. A small change in the `print` function could break countless programs. If we extract a common part from A and B, we have an easier job in the future if we need to change them both, but it’s harder and more error-prone when we need to change only one. 8 | 9 | This chapter is dedicated to reusability. It touches on many subjects that developers do intuitively. We do so because we learned them through practice. Mainly through observations of how something we did in the past has an impact on us now. We extracted something, and now it causes problems. We haven’t extracted something, and now we have a problem when we need to make some changes. Sometimes we deal with code written by different developers years ago, and we see how their decisions impact us now. Maybe we just looked at another language or project and we thought “Wow, this is short and readable because they did X”. This is the way we typically learn, and this is one of the best ways to learn. 10 | 11 | It has one problem though: it requires years of practice. To speed it up and to help you systematize this knowledge, this chapter will give you some generic rules to help you make your code better in the long term. It is a bit more theoretical than the rest of the book. If you’re looking for concrete rules (as presented in the previous chapters), feel free to skip it. -------------------------------------------------------------------------------- /Part 2 Code design/Chapter 3 Reusability/Item 22 Use generics when implementing common algorithms.md: -------------------------------------------------------------------------------- 1 | ## Item 22: Use generics when implementing common algorithms 2 | 3 | Similarly, as we can pass a value to a function as an argument, we can pass a type as a type argument. Functions that accept type arguments (so having type parameters) are called generic functions. One known example is the `filter` function from stdlib that has type parameter `T`: 4 | 5 | ``` kotlin 6 | inline fun Iterable.filter( 7 | predicate: (T) -> Boolean 8 | ): List { 9 | val destination = ArrayList() 10 | for (element in this) { 11 | if (predicate(element)) { 12 | destination.add(element) 13 | } 14 | } 15 | return destination 16 | } 17 | ``` 18 | 19 | Type parameters are useful to the compiler since they allow it to check and correctly infer types a bit further, what makes our programs safer and programming more pleasurable for developers. For instance, when we use `filter`, inside the lambda expression, the compiler knows that an argument is of the same type as the type of elements in the collection, so it protects us from using something illegal and the IDE can give us useful suggestions. 20 | 21 | ![](../../assets/chapter1/chapter1-3.png) 22 | 23 | Generics were primarily introduced to classes and interfaces to allow the creation of collections with only concrete types, like `List` or `Set`. Those types are lost during compilation but when we are developing, the compiler forces us to pass only elements of the correct type. For instance `Int` when we add to `MutableList`. Also, thanks to them, the compiler knows that the returned type is `User` when we get an element from `Set`. This way type parameters help us a lot in statically-typed languages. Kotlin has powerful support for generics that is not well understood and from my experience even experienced Kotlin developers have gaps in their knowledge especially about variance modifiers. So let’s discuss the most important aspects of Kotlin generics in this and in *Item 24: Consider variance for generic types*. 24 | 25 | ### Generic constraints 26 | 27 | One important feature of type parameters is that they can be constrained to be a subtype of a concrete type. We set a constraint by placing supertype after a colon. This type can include previous type parameters: 28 | 29 | ``` kotlin 30 | fun > Iterable.sorted(): List { 31 | /*...*/ 32 | } 33 | 34 | fun > 35 | Iterable.toCollection(destination: C): C { 36 | /*...*/ 37 | } 38 | 39 | class ListAdapter(/*...*/) { /*...*/ } 40 | ``` 41 | 42 | One important result of having a constraint is that instances of this type can use all the methods this type offers. This way when `T` is constrained as a subtype of `Iterable`, we know that we can iterate over an instance of type `T`, and that elements returned by the iterator will be of type `Int`. When we constraint to `Comparable`, we know that this type can be compared with itself. Another popular choice for a constraint is `Any` which means that a type can be any non-nullable type: 43 | 44 | ``` kotlin 45 | inline fun Iterable.mapNotNull( 46 | transform: (T) -> R? 47 | ): List { 48 | return mapNotNullTo(ArrayList(), transform) 49 | } 50 | ``` 51 | 52 | In rare cases in which we might need to set more than one upper bound, we can use `where` to set more constraints: 53 | 54 | ``` kotlin 55 | fun pet(animal: T) where T: GoodTempered { 56 | /*...*/ 57 | } 58 | 59 | // OR 60 | 61 | fun pet(animal: T) where T: Animal, T: GoodTempered { 62 | /*...*/ 63 | } 64 | ``` 65 | 66 | ### Summary 67 | 68 | Type parameters are an important part of Kotlin typing system. We use them to have type-safe generic algorithms or generic objects. Type parameters can be constrained to be a subtype of a concrete type. When they are, we can safely use methods offered by this type. -------------------------------------------------------------------------------- /Part 2 Code design/Chapter 3 Reusability/Item 23 Avoid shadowing type parameters.md: -------------------------------------------------------------------------------- 1 | ## Item 23: Avoid shadowing type parameters 2 | 3 | It is possible to define property and parameters with the same name due to shadowing. Local parameter shadows outer scope property. There is no warning because such a situation is not uncommon and is rather visible for developers: 4 | 5 | ``` kotlin 6 | class Forest(val name: String) { 7 | 8 | fun addTree(name: String) { 9 | // ... 10 | } 11 | } 12 | ``` 13 | 14 | On the other hand, the same can happen when we shadow class type parameter with a function type parameter. Such a situation is less visible and can lead to serious problems. This mistake is often done by developers not understanding well how generics work. 15 | 16 | ``` kotlin 17 | interface Tree 18 | class Birch: Tree 19 | class Spruce: Tree 20 | 21 | class Forest { 22 | 23 | fun addTree(tree: T) { 24 | // ... 25 | } 26 | } 27 | ``` 28 | 29 | The problem is that now `Forest` and `addTree` type parameters are independent of each other: 30 | 31 | ``` kotlin 32 | val forest = Forest() 33 | forest.addTree(Birch()) 34 | forest.addTree(Spruce()) 35 | ``` 36 | 37 | Such situation is rarely desired and might be confusing. One solution is that `addTree` should use the class type parameter `T`: 38 | 39 | ``` kotlin 40 | class Forest { 41 | 42 | fun addTree(tree: T) { 43 | // ... 44 | } 45 | } 46 | 47 | // Usage 48 | val forest = Forest() 49 | forest.addTree(Birch()) 50 | forest.addTree(Spruce()) // ERROR, type mismatch 51 | ``` 52 | 53 | If we need to introduce a new type parameter, it is better to name it differently. Note that it can be constrained to be a subtype of the other type parameter: 54 | 55 | ``` kotlin 56 | class Forest { 57 | 58 | fun addTree(tree: ST) { 59 | // ... 60 | } 61 | } 62 | ``` 63 | 64 | ### Summary 65 | 66 | Avoid shadowing type parameters, and be careful when you see that type parameter is shadowed. Unlike for other kinds of parameters, it is not intuitive and might be highly confusing. -------------------------------------------------------------------------------- /Part 2 Code design/Chapter 3 Reusability/Item 25 Reuse between different platforms by extracting common modules.md: -------------------------------------------------------------------------------- 1 | ## Item 25: Reuse between different platforms by extracting common modules 2 | 3 | Companies rarely write applications only for just a single platform. They would rather develop a product for two or more platforms, and their products, nowadays, often rely on several applications running on different platforms. Think of client and server applications communicating through network calls. As they need to communicate, there are often similarities that can be reused. Implementations of the same product for different platforms generally have even more similarities. Especially their business logic is often nearly identical. These projects can profit significantly from sharing code. 4 | 5 | ### Full-stack development 6 | 7 | Lots of companies are based on web development. Their product is a website, but in most cases, those products need a backend application (also called server-side). On websites, JavaScript is the king. It nearly has a monopoly on this platform. On the backend, a very popular option (if not the most popular) is Java. Since these languages are very different, it is common that backend and web development are separated. Things can change, however. Now Kotlin is becoming a popular alternative to Java for backend development. For instance with Spring, the most popular Java framework, Kotlin is a first-class citizen. Kotlin can be used as an alternative to Java in every framework. There are also many Kotlin backend frameworks like Ktor. This is why many backend projects migrate from Java to Kotlin. A great part of Kotlin is that it can also be compiled into JavaScript. There are already many Kotlin/JS libraries, and we can use Kotlin to write different kinds of web applications. For instance, we can write a web frontend using the React framework and Kotlin/JS. This allows us to write both the backend and the website all in Kotlin. What is even better, **we can have parts that compile both to JVM bytecode and to JavaScript.** Those are shared parts. We can place there, for instance, universal tools, API endpoint definitions, common abstractions, etc. that we might want to reuse. 8 | 9 | ![img](../../assets/chapter3/chapter3-7.png) 10 | 11 | ### Mobile development 12 | 13 | This capability is even more important in the mobile world. We rarely build only for Android. Sometimes we can live without a server, but we generally need to implement an iOS application as well. Each application is written for a different platform using different languages and tools. In the end, Android and iOS versions of the same application are very similar. They are often designed differently, but they nearly always have the same logic inside. Using Kotlin multiplatform capabilities, we can implement this logic only once and reuse it between those two platforms. We can make a common module and implement business logic there. Business logic should be independent of frameworks and platforms anyway (Clean Architecture). Such common logic can be written in pure Kotlin or using other common modules, and it can then be used on different platforms. 14 | 15 | In Android, it can be used directly since Android is built the same way using Gradle. The experience is similar to having those common parts in our Android project. 16 | 17 | For iOS, we compile these common parts to an Objective-C framework using Kotlin/Native which is compiled using LLVM. We can then use it from Swift in Xcode or AppCode. Alternatively, we can implement our whole application using Kotlin/Native. 18 | 19 | ![](../../assets/chapter3/chapter3-8.png) 20 | 21 | ### Libraries 22 | 23 | Defining common modules is also a powerful tool for libraries. In particular, those that do not depend strongly on platform can easily be moved to a common module and allow users to use them from all languages running on the JVM, JavaScript or natively (so from Java, Scala, JavaScript, CoffeeScript, TypeScript, C, Objective-C, Swift, Python, C#, etc.). 24 | 25 | ### All together 26 | 27 | We can use all those platforms together. Using Kotlin, we can develop for nearly all kinds of popular devices and platforms, and reuse code between them however we want. Just a few examples of what we can write in Kotlin: 28 | 29 | - Backend in Kotlin/JVM, for instance on Spring or Ktor 30 | - Website in Kotlin/JS, for instance in React 31 | - Android in Kotlin/JVM, using the Android SDK 32 | - iOS Frameworks that can be used from Objective-C or Swift, using Kotlin/Native 33 | - Desktop applications in Kotlin/JVM, for instance in TornadoFX 34 | - Raspberry Pi, Linux or Mac OS programs in Kotlin/Native 35 | 36 | Here is a typical application visualized: 37 | 38 | ![](../../assets/chapter3/chapter3-9.png) 39 | 40 | We are still learning how to organize our code to make code reuse safe and efficient in the long run. But it is good to know the possibilities this approach gives us. We can reuse between different platforms using common modules. This is a powerful tool to eliminate redundancy and to reuse common logic and common algorithms. -------------------------------------------------------------------------------- /Part 2 Code design/Chapter 4 Abstraction design/Item 29 Consider wrapping external API.md: -------------------------------------------------------------------------------- 1 | ## Item 29: Consider wrapping external API 2 | 3 | It is risky to heavily use an API that might be unstable. Both when creators clarify that it is unstable, and when we do not trust those creators to keep it stable. Remembering that we need to adjust every use in case of inevitable API change, we should consider limiting uses and separate them from our logic as much as possible. This is why we often wrap potentially unstable external library APIs in our own project. This gives us a lot of freedom and stability: 4 | 5 | - We are not afraid of API changes because we would only need to change a single usage inside the wrapper. 6 | - We can adjust the API to our project style and logic. 7 | - We can replace it with a different library in case of some problems with this one. 8 | - We can change the behavior of these objects if we need to (of course, do it responsibly). 9 | 10 | There are also counterarguments to this approach: 11 | 12 | - We need to define all those wrappers. 13 | - Our internal API is internal, and developers need to learn it just for this project. 14 | - There are no courses teaching how our internal API works. We should also not expect answers on Stack Overflow. 15 | 16 | Knowing both sides, you need to decide which APIs should be wrapped. A good heuristics that tells us how stable a library is are version number and the number of users. Generally, the more users the library has, the more stable it is. Creators are more careful with changes when they know that their small change might require corrections in many projects. The riskiest libraries are new ones with small popularity. Use them wisely and consider wrapping them into your own classes and functions to control them internally. -------------------------------------------------------------------------------- /Part 2 Code design/Chapter 4 Abstraction design/Item 32 Respect abstraction contracts.md: -------------------------------------------------------------------------------- 1 | ## Item 32: Respect abstraction contracts 2 | 3 | Both contract and visibility are kind of an agreement between developers. This agreement nearly always can be violated by a user. Technically, everything in a single project can be hacked. For instance, it is possible to use reflection to open and use anything we want: 4 | 5 | ``` kotlin 6 | class Employee { 7 | private val id: Int = 2 8 | override fun toString() = "User(id=$id)" 9 | 10 | private fun privateFunction() { 11 | println("Private function called") 12 | } 13 | } 14 | 15 | fun callPrivateFunction(employee: Employee) { 16 | employee::class.declaredMemberFunctions 17 | .first { it.name == "privateFunction" } 18 | .apply { isAccessible = true } 19 | .call(employee) 20 | } 21 | 22 | fun changeEmployeeId(employee: Employee, newId: Int) { 23 | employee::class.java.getDeclaredField("id") 24 | .apply { isAccessible = true } 25 | .set(employee, newId) 26 | } 27 | 28 | fun main() { 29 | val employee = Employee() 30 | callPrivateFunction(employee) 31 | // Prints: Private function called 32 | 33 | changeEmployeeId(employee, 1) 34 | print(employee) // Prints: User(id=1) 35 | } 36 | ``` 37 | 38 | Just because you can do something, doesn’t mean that it is fine to do it. Here we very strongly depend on the implementation details like the names of the private property and the private function. They are not part of a contract at all, and so they might change at any moment. This is like a ticking bomb for our program. 39 | 40 | Remember that a contract is like a warranty. As long as you use your computer correctly, the warranty protects you. When you open your computer and start hacking it, you lose your warranty. The same principle applies here: when you break the contract, it is your problem when implementation changes and your code stops working. 41 | 42 | ### Contracts are inherited 43 | 44 | It is especially important to respect contracts when we inherit from classes, or when we extend interfaces from another library. Remember that your object should respect their contracts. For instance, every class extends `Any` that have `equals`and `hashCode` methods. They both have well-established contracts that we need to respect. If we don’t, our objects might not work correctly. For instance, when `hashCode` is not consistent with `equals`, our object might not behave correctly on `HashSet`. Below behavior is incorrect because a set should not allow duplicates: 45 | 46 | ``` kotlin 47 | class Id(val id: Int) { 48 | override fun equals(other: Any?) = 49 | other is Id && other.id == id 50 | } 51 | 52 | val mutableSet = mutableSetOf(Id(1)) 53 | mutableSet.add(Id(1)) 54 | mutableSet.add(Id(1)) 55 | print(mutableSet.size) // 3 56 | ``` 57 | 58 | In this case, it is that `hashCode` do not have implementation consistent with `equals`. We will discuss some important Kotlin contracts in *Chapter 6: Class design*. For now, remember to check the expectations on functions you override, and respect those. 59 | 60 | ### Summary 61 | 62 | If you want your programs to be stable, respect contracts. If you are forced to break them, document this fact well. Such information will be very helpful to whoever will maintain your code. Maybe that will be you, in a few years’ time. -------------------------------------------------------------------------------- /Part 2 Code design/Chapter 5 Object creation/Introduction.md: -------------------------------------------------------------------------------- 1 | # Chapter 5: Object creation 2 | 3 | Although Kotlin can be written in a purely functional style, it can also be written in object oriented programming (OOP), much like Java. In OOP, we need to create every object we use, or at least define how it ought to be created, and different ways have different characteristics. It is important to know what options do we have. This is why this chapter shows different ways how we can define object creation, and explains their advantages and disadvantages. 4 | 5 | If you are familiar with the *Effective Java* book by Joshua Bloch, then you may notice some similarities between this chapter and that book. It is no coincidence. This chapter relates to the first chapter of Effective Java. Although Kotlin is very different from Java, and there are only morsels of knowledge that can be used. For instance, static methods are not allowed in Kotlin, but we have very good alternatives like top-level functions and companion object functions. They don’t work the same way as static functions, so it is important to understand them. Similarly, with other items, you can notice similarities, but the changes that Kotlin has introduced are important. To cheer you up: these changes are mostly to provide more possibilities or force better style. Kotlin is a powerful and really well-designed language, and this chapter should mainly open your eyes to these new possibilities. -------------------------------------------------------------------------------- /Part 2 Code design/Chapter 6 Class design/Introduction.md: -------------------------------------------------------------------------------- 1 | # Chapter 6: Class design 2 | 3 | Classes are the most important abstraction in the Object-Oriented Programming (OOP) paradigm. Since OOP is the most popular paradigm in Kotlin, classes are very important for us as well. This chapter is about class design. Not about system design, since it would require much more space and there are already many great books on this topic such as Clean Architecture by Robert C. Martin or Design Patterns by Erich Gamma, John Vlissides, Ralph Johnson, and Richard Helm. Instead, we will mainly talk about contracts that Kotlin classes are expected to fulfill - how we use Kotlin structures and what is expected from us when we use them. When and how should we use inheritance? How do we expect data classes to be used? When should we use function types instead of interfaces with a single method? What are the contracts of `equals`, `hashCode` and `compareTo`? When should we use extensions instead of members? These are the kind of questions we will answer here. They are all important because breaking them might cause serious problems, and following them will help you make your code safer and cleaner. -------------------------------------------------------------------------------- /Part 2 Code design/Chapter 6 Class design/Item 38 Use function types instead of interfaces to pass operations and actions.md: -------------------------------------------------------------------------------- 1 | ## Item 38: Use function types instead of interfaces to pass operations and actions 2 | 3 | Many languages do not have the concept of a function type. Instead, they use interfaces with a single method. Such interfaces are known as SAM’s (Single-Abstract Method). Here is an example SAM used to pass information about what should happen when a view is clicked: 4 | 5 | ``` kotlin 6 | interface OnClick { 7 | fun clicked(view: View) 8 | } 9 | ``` 10 | 11 | When a function expects a SAM, we must pass an instance of an object that implements this interface[1](chap65.xhtml#fn-footnote_610_note). 12 | 13 | ``` kotlin 14 | fun setOnClickListener(listener: OnClick) { 15 | //... 16 | } 17 | 18 | setOnClickListener(object : OnClick { 19 | override fun clicked(view: View) { 20 | // ... 21 | } 22 | }) 23 | ``` 24 | 25 | However, notice that declaring a parameter with a function type gives us much more freedom: 26 | 27 | ``` kotlin 28 | fun setOnClickListener(listener: (View) -> Unit) { 29 | //... 30 | } 31 | ``` 32 | 33 | Now, we can pass the parameter as: 34 | 35 | - A lambda expression or an anonymous function 36 | 37 | ``` kotlin 38 | setOnClickListener { /*...*/ } 39 | setOnClickListener(fun(view) { /*...*/ }) 40 | ``` 41 | 42 | - A function reference or bounded function reference 43 | 44 | ``` kotlin 45 | setOnClickListener(::println) 46 | setOnClickListener(this::showUsers) 47 | ``` 48 | 49 | - Objects that implement the declared function type 50 | 51 | ``` kotlin 52 | class ClickListener: (View)->Unit { 53 | override fun invoke(view: View) { 54 | // ... 55 | } 56 | } 57 | 58 | setOnClickListener(ClickListener()) 59 | ``` 60 | 61 | These options can cover a wider spectrum of use cases. On the other hand, one might argue that the advantage of a SAM is that it and its arguments are named. Notice that we can name function types using type aliases as well. 62 | 63 | ``` kotlin 64 | typealias OnClick = (View) -> Unit 65 | ``` 66 | 67 | Parameters can also be named. The advantage of naming them is that these names can then be suggested by default by an IDE. 68 | 69 | ``` kotlin 70 | fun setOnClickListener(listener: OnClick) { /*...*/ } 71 | typealias OnClick = (view: View)->Unit 72 | ``` 73 | 74 | ![](../../assets/chapter6/chapter6-3.png) 75 | 76 | Notice that when we use lambda expressions, we can also destructure arguments. Together, this makes function types generally a better option than SAMs. 77 | 78 | This argument is especially true when we set many observers. The classic Java way often is to collect them in a single listener interface: 79 | 80 | ``` kotlin 81 | class CalendarView { 82 | var listener: Listener? = null 83 | 84 | interface Listener { 85 | fun onDateClicked(date: Date) 86 | fun onPageChanged(date: Date) 87 | } 88 | } 89 | ``` 90 | 91 | I believe this is largely a result of laziness. From an API consumer’s point of view, it is better to set them as separate properties holding function types: 92 | 93 | ``` kotlin 94 | class CalendarView { 95 | var onDateClicked: ((date: Date) -> Unit)? = null 96 | var onPageChanged: ((date: Date) -> Unit)? = null 97 | } 98 | ``` 99 | 100 | This way, the implementations of `onDateClicked` and `onPageChanged` do not need to be tied together in an interface. Now, these functions may be changed independently. 101 | 102 | If you don’t have a good reason to define an interface, prefer using function types. They are well supported and are used frequently by Kotlin developers. 103 | 104 | ### When should we prefer a SAM? 105 | 106 | There is one case when we prefer a SAM: When we design a class to be used from another language than Kotlin. Interfaces are cleaner for Java clients. They cannot see type aliases nor name suggestions. Finally, Kotlin function types when used from some languages (especially Java) require functions to return `Unit` explicitly: 107 | 108 | ``` kotlin 109 | // Kotlin 110 | class CalendarView() { 111 | var onDateClicked: ((date: Date) -> Unit)? = null 112 | var onPageChanged: OnDateClicked? = null 113 | } 114 | 115 | interface OnDateClicked { 116 | fun onClick(date: Date) 117 | } 118 | 119 | // Java 120 | CalendarView c = new CalendarView(); 121 | c.setOnDateClicked(date -> Unit.INSTANCE); 122 | c.setOnPageChanged(date -> {}); 123 | ``` 124 | 125 | This is why it might be reasonable to use SAM instead of function types when we design API for use from Java. Though in other cases, prefer function types. -------------------------------------------------------------------------------- /Part 2 Code design/Chapter 6 Class design/Item 44 Avoid member extensions.md: -------------------------------------------------------------------------------- 1 | ## Item 44: Avoid member extensions 2 | 3 | When we define an extension function to some class, it is not added to this class as a member. An extension function is just a different kind of function that we call on the first argument that is there, called a receiver. Under the hood, extension functions are compiled to normal functions, and the receiver is placed as the first parameter. For instance, the following function: 4 | 5 | ``` kotlin 6 | fun String.isPhoneNumber(): Boolean = 7 | length == 7 && all { it.isDigit() } 8 | ``` 9 | 10 | Under the hood is compiled to a function similar to this one: 11 | 12 | ``` kotlin 13 | fun isPhoneNumber(`$this`: String): Boolean = 14 | `$this`.length == 7 && `$this`.all { it.isDigit() } 15 | ``` 16 | 17 | One of the consequences of how they are implemented is that we can have member extensions or even define extensions in interfaces: 18 | 19 | ``` kotlin 20 | interface PhoneBook { 21 | fun String.isPhoneNumber(): Boolean 22 | } 23 | 24 | class Fizz: PhoneBook { 25 | override fun String.isPhoneNumber(): Boolean = 26 | length == 7 && all { it.isDigit() } 27 | } 28 | ``` 29 | 30 | Even though it is possible, there are good reasons to avoid defining member extensions (except for DSLs). **Especially, do not define extension as members just to restrict visibility**. 31 | 32 | ``` kotlin 33 | // Bad practice, do not do this 34 | class PhoneBookIncorrect { 35 | // ... 36 | 37 | fun String.isPhoneNumber() = 38 | length == 7 && all { it.isDigit() } 39 | } 40 | ``` 41 | 42 | One big reason is that it does not really restrict visibility. It only makes it more complicated to use the extension function since the user would need to provide both the extension and dispatch receivers: 43 | 44 | ``` kotlin 45 | PhoneBookIncorrect().apply { "1234567890".test() } 46 | ``` 47 | 48 | **You should restrict the extension visibility using a visibility modifier and not by making it a member.** 49 | 50 | ``` kotlin 51 | // This is how we limit extension functions visibility 52 | class PhoneBookCorrect { 53 | // ... 54 | } 55 | 56 | private fun String.isPhoneNumber() = 57 | length == 7 && all { it.isDigit() } 58 | ``` 59 | 60 | There are a few good reasons why we prefer to avoid member extensions: 61 | 62 | - Reference is not supported: 63 | 64 | ``` kotlin 65 | val ref = String::isPhoneNumber 66 | val str = "1234567890" 67 | val boundedRef = str::isPhoneNumber 68 | 69 | val refX = PhoneBookIncorrect::isPhoneNumber // ERROR 70 | val book = PhoneBookIncorrect() 71 | val boundedRefX = book::isPhoneNumber // ERROR 72 | ``` 73 | 74 | - Implicit access to both receivers might be confusing: 75 | 76 | ``` kotlin 77 | class A { 78 | val a = 10 79 | } 80 | class B { 81 | val a = 20 82 | val b = 30 83 | 84 | fun A.test() = a + b // Is it 40 or 50? 85 | } 86 | ``` 87 | 88 | - When we expect an extension to modify or reference a receiver, it is not clear if we modify the extension or dispatch receiver (the class in which the extension is defined): 89 | 90 | ``` kotlin 91 | class A { 92 | //... 93 | } 94 | class B { 95 | //... 96 | 97 | fun A.update() = ... // Does it update A or B? 98 | } 99 | ``` 100 | 101 | - For less experienced developers it might be counterintuitive or scary to see member extensions. 102 | 103 | To summarize, if there is a good reason to use a member extension, it is fine. Just be aware of the downsides and generally try to avoid it. To restrict visibility, use visibility modifiers. Just placing an extension in a class does not limit its use from outside. -------------------------------------------------------------------------------- /Part 3 Efficiency/Chapter 7 Make it cheap/Introduction.md: -------------------------------------------------------------------------------- 1 | # Chapter 7: Make it cheap 2 | 3 | Code efficiency today is often treated indulgently. To a certain degree, it is reasonable. Memory is cheap and developers are expensive. Though if your application is running on millions of devices, it consumes a lot of energy, and some optimization of battery use might save enough energy to power a small city. Or maybe your company is paying lots of money for servers and their maintenance, and some optimization might make it significantly cheaper. Or maybe your application works well for a small number of requests but does not scale well and on the day of the trial, it shuts down. Customers remember such situations. 4 | 5 | Efficiency is important in the long term, but optimization is not easy. Premature optimization often does more harm than good. Instead, there are some rules that can help you make more efficient programs nearly painlessly. Those are the cheap wins: they cost nearly nothing, but still, they can help us improve performance significantly. When they are not sufficient, we should use a profiler and optimize performance-critical parts. This is more difficult to achieve because it requires more understanding of what is expensive and how some optimizations can be done. 6 | 7 | This and the next chapter are about performance: 8 | 9 | - *Chapter 7: Make it cheap* - more general suggestions for performance. 10 | - *Chapter 8: Efficient collection processing - concentrates on collection processing.* 11 | 12 | They focus on general rules to cheaply optimize every-day development. But they also give some Kotlin-specific suggestions on how performance might be optimized in the critical parts of your program. They should also deepen your understanding of thinking about performance in general. 13 | 14 | Please, remember that when there is a tradeoff between readability and performance, you need to answer yourself what is more important in the component you develop. I included some suggestions, but there is no universal answer. -------------------------------------------------------------------------------- /Part 3 Efficiency/Chapter 8 Efficient collection processing/Introduction.md: -------------------------------------------------------------------------------- 1 | # Chapter 8: Efficient collection processing 2 | 3 | Collections are one of the most important concepts in programming. In iOS, one of the most important view elements, `UICollectionView`, is designed to represent a collection. Similarly, in Android, it is hard to imagine an application without `RecyclerView` or `ListView`. When you need to write a portal with news, you will have a list of news. Each of them will probably have a list of authors and a list of tags. When you make an online shop, you start from a list of products. They will most likely have a list of categories and a list of different variants. When a user buys, they use some basket which is probably a collection of products and amounts. Then they need to choose from a list of delivery options and a list of payment methods. In some languages, `String` is just a list of characters. Collections are everywhere in programming! Just think about your application and you will quickly see lots of collections. 4 | 5 | This fact can be reflected also in programming languages. Most modern languages have some collection literals: 6 | 7 | ``` python 8 | 1 // Python 9 | 2 primes = [2, 3, 5, 7, 13] 10 | 3 // Swift 11 | 4 let primes = [2, 3, 5, 7, 13] 12 | ``` 13 | 14 | Good collection processing was a flag functionality of functional programming languages. Name of the Lisp programming language[1](chap65.xhtml#fn-footnote_80_note) stands for “list processing”. Most modern languages have good support for collection processing. This statement includes Kotlin which has one of the most powerful sets of tools for collection processing. Just think of the following collection processing: 15 | 16 | ``` kotlin 17 | val visibleNews = mutableListOf() 18 | for (n in news) { 19 | if(n.visible) { 20 | visibleNews.add(n) 21 | } 22 | } 23 | 24 | Collections.sort(visibleNews, 25 | { n1, n2 -> n2.publishedAt - n1.publishedAt }) 26 | val newsItemAdapters = mutableListOf() 27 | for (n in visibleNews) { 28 | newsItemAdapters.add(NewsItemAdapter(n)) 29 | } 30 | ``` 31 | 32 | In Kotlin it can be replaced with the following notation: 33 | 34 | ``` kotlin 35 | val newsItemAdapters = news 36 | .filter { it.visible } 37 | .sortedByDescending { it.publishedAt } 38 | .map(::NewsItemAdapter) 39 | ``` 40 | 41 | Such notation is not only shorter, but it is also more readable. Every step makes a concrete transformation on the list of elements. Here is a visualization of the above processing: 42 | 43 | ![](../../assets/chapter8/chapter8-1.png) 44 | 45 | The performance of the above examples is very similar. It is not always so simple though. Kotlin has a lot of collection processing methods and we can do the same processing in a lot of different ways. For instance, the below processing implementations have the same result but different performance: 46 | 47 | ``` kotlin 48 | fun productsListProcessing(): String { 49 | return clientsList 50 | .filter { it.adult } 51 | .flatMap { it.products } 52 | .filter { it.bought } 53 | .map { it.price } 54 | .filterNotNull() 55 | .map { "$$it" } 56 | .joinToString(separator = " + ") 57 | } 58 | 59 | fun productsSequenceProcessing(): String { 60 | return clientsList.asSequence() 61 | .filter { it.adult } 62 | .flatMap { it.products.asSequence() } 63 | .filter { it.bought } 64 | .mapNotNull { it.price } 65 | .joinToString(separator = " + ") { "$$it" } 66 | } 67 | ``` 68 | 69 | Collection processing optimization is much more than just a brain-puzzle. Collection processing is extremely important and, in big systems, often performance-critical. This is why it is often key to improve the performance of our program. Especially if we do backend application development or data analysis. Although, when we are implementing front-end clients, we can face collection processing that limits the performance of our application as well. As a consultant, I’ve seen a lot of projects and my experience is that I see collection processing again and again in lots of different places. This is not something that can be ignored so easily. 70 | 71 | The good news is that collection processing optimization is not hard to master. There are some rules and a few things to remember but actually, anyone can do it effectively. This is what we are going to learn in this chapter. -------------------------------------------------------------------------------- /Part 3 Efficiency/Chapter 8 Efficient collection processing/Item 50 Limit the number of operations.md: -------------------------------------------------------------------------------- 1 | ## 第50条: 减少操作的次数 2 | 3 | 每个集合处理的方法都伴随着性能开销。对于标准库中提供的集合处理方法,在底层实现上,通常是对集合元素的一次迭代和新集合的创建。而对于序列处理,它会创建一个新的对象来持有和操作整个序列。尤其是当处理的元素数量很多的时候,这两者的性能开销都会变的很大。因此,我们应该减少处理步骤的数量,主要可以通过使用复合操作来做到这一点。例如,我们使用 `filterNotNull`,而不是先过滤非空类型然后再转换为不可空类型。或者我们可以只使用`mapNotNull`而不是先进行映射之后再进行过滤操作。 4 | 5 | ``` kotlin 6 | class Student(val name: String?) 7 | 8 | // Works 9 | fun List.getNames(): List = this 10 | .map { it.name } 11 | .filter { it != null } 12 | .map { it!! } 13 | 14 | // Better 15 | fun List.getNames(): List = this 16 | .map { it.name } 17 | .filterNotNull() 18 | 19 | // Best 20 | fun List.getNames(): List = this 21 | .mapNotNull { it.name } 22 | ``` 23 | 24 | 通常来说,最大的问题不是缺乏对这一问题的认知,而是缺乏对应该使用哪些集合处理函数的了解。 这是学习它们很好的另一个原因。 此外,IDE也会提供有用的警告,这些警告经常给我们更好的替代方案的建议。 25 | 26 | ![](../../assets/chapter8/chapter8-6.png) 27 | 28 | 尽管如此,了解如何替代复合操作还是很有必要的。 下面列出了一些常见的函数调用和限制操作数量的替代方法: 29 | 30 | ![](../../assets/chapter8/chapter8-7.png) 31 | 32 | ### 总结 33 | 34 | 大多数集合处理步骤需要迭代整个集合和额外的集合创建。 这个成本可以通过使用更合适的函数来限制。 -------------------------------------------------------------------------------- /Part 3 Efficiency/Chapter 8 Efficient collection processing/Item 51 Consider Arrays with primitives for performance-critical processing.md: -------------------------------------------------------------------------------- 1 | ## 第51条:在“性能优先”的场景,使用基础类型数组 2 | 3 | 我们不能在Kotlin中声明基础类型,但是作为一种优化手段,在底层经常会使用它。这是一个重要的优化,在*第45项:避免不必要的对象创建*中已经讲述过。基础类型是: 4 | 5 | - 更轻量级的,因为每个对象都会增加额外的内存开销 6 | - 更快,因为通过getter方法访问值有额外的性能开销 7 | 8 | 因此,对量较大的数据使用基础类型可能是一个重要的优化。但有一个问题是,在Kotlin中,典型的集合,如`List`或`Set`,使用的是泛型类型。基础类型不能作为泛型参数使用,因此我们最终使用的是包装类型。这是一个方便的解决方案,适合大多数情况,因为在标准集合上更容易做处理操作。尽管如此,在代码中对性能有重要要求的部分,我们应该考虑使用基础类型数组,比如`IntArray`或`LongArray`,因为它们的内存占用更少,处理效率更高。 9 | 10 | | Kotlin type | Java type | 11 | | :---------- | :------------ | 12 | | Int | int | 13 | | List | List | 14 | | Array | Integer[] | 15 | | IntArray | int[] | 16 | 17 | 基础类型数组有多轻量级呢? 假设在Kotlin/JVM中,我们需要保存1 000 000个整数,我们可以选择将它们保存在`IntArray`或`List`中。当你对他们的内存情况进行计算时,你会发现`IntArray`分配了4 000 016字节的内存,而`List`分配了20 000 040字节,足足5倍的差距。 如果你需要做内存优化,并且你的集合持有的数据类型是基础类型, 如`Int`,那么你可以尝试使用基础类型数组来优化。 18 | 19 | ``` kotlin 20 | import jdk.nashorn.internal.ir.debug.ObjectSizeCalculator.getObjectSize 21 | 22 | fun main() { 23 | val ints = List(1_000_000) { it } 24 | val array: Array = ints.toTypedArray() 25 | val intArray: IntArray = ints.toIntArray() 26 | println(getObjectSize(ints)) // 20 000 040 27 | println(getObjectSize(array)) // 20 000 016 28 | println(getObjectSize(intArray)) // 4 000 016 29 | } 30 | ``` 31 | 32 | 在性能上同样也有差别,对于同样的1 000 000个数字的集合,在计算平均值时,对基础类型数组的处理要快25%左右。 33 | 34 | ``` kotlin 35 | open class InlineFilterBenchmark { 36 | 37 | lateinit var list: List 38 | lateinit var array: IntArray 39 | 40 | @Setup 41 | fun init() { 42 | list = List(1_000_000) { it } 43 | array = IntArray(1_000_000) { it } 44 | } 45 | 46 | @Benchmark 47 | // On average 1 260 593 ns 48 | fun averageOnIntList(): Double { 49 | return list.average() 50 | } 51 | 52 | @Benchmark 53 | // On average 868 509 ns 54 | fun averageOnIntArray(): Double { 55 | return array.average() 56 | } 57 | } 58 | ``` 59 | 60 | 正如你所看到的,使用基础类型和基础类型数组可以作为代码中优化性能的一种手段。它们占用的内存更少,处理速度也更快。虽然在大多数情况下,这种优化带来的效果并不明显,不足以让我们默认使用基础类型数组而不是`List`。`List`更直观,使用频率更高,所以在大多数情况下,我们应该使用它。但你仍需记住这种优化,以防你需要优化一些”性能优先“的部分。 61 | 62 | ### 总结 63 | 64 | 在一般情况下,应该优先使用`List`或`Set`而不是`Array`。但如果你需要保存大量的基础类型的数据,使用`Array`可能会显著提高你的性能和内存使用情况。这一条特别适用于开发基础库或编写游戏或高级图形处理的开发者。 -------------------------------------------------------------------------------- /Part 3 Efficiency/Chapter 8 Efficient collection processing/Item 52 Consider using mutable collections.md: -------------------------------------------------------------------------------- 1 | ## 第52条:在处理局部变量时,考虑使用可变集合 2 | 3 | 使用可变集合而不是不可变集合的最大优势是,它们在性能上表现更好。当我们向一个不可变的集合添加一个元素时,我们需要创建一个新的集合并将所有的元素添加到其中。下面是目前在Kotlin stdlib(Kotlin 1.2)中的实现方式: 4 | 5 | ``` kotlin 6 | operator fun Iterable.plus(element: T): List { 7 | if (this is Collection) return this.plus(element) 8 | val result = ArrayList() 9 | result.addAll(this) 10 | result.add(element) 11 | return result 12 | } 13 | ``` 14 | 15 | 当我们处理数据量较大的集合时,添加前一个集合中的所有元素是一个巨大的性能开销。这就是为什么使用可变集合是一种性能优化,特别是当我们需要添加元素时。另一方面,*第1条:限制可变性*告诉我们使用不可变的集合来保证安全的好处。不过请注意,这些论点很少适用于局部变量,因为局部变量很少需要同步或封装。这就是为什么对于局部变量处理来说,通常使用可变集合更有意义。这一论点可以在标准库中得到证实,所有的集合处理函数在内部都是使用可变集合实现的: 16 | 17 | ``` kotlin 18 | inline fun Iterable.map( 19 | transform: (T) -> R 20 | ): List { 21 | val size = if (this is Collection<*>) this.size else 10 22 | val destination = ArrayList(size) 23 | for (item in this) 24 | destination.add(transform(item)) 25 | return destination 26 | } 27 | ``` 28 | 29 | 而不是使用不可变的集合: 30 | 31 | ``` kotlin 32 | // This is not how map is implemented 33 | inline fun Iterable.map( 34 | transform: (T) -> R 35 | ): List { 36 | var destination = listOf() 37 | for (item in this) 38 | destination += transform(item) 39 | return destination 40 | } 41 | ``` 42 | 43 | ### 总结 44 | 45 | 使用可变集合来添加元素通常有更好的性能表现,但是不可变集合给了我们更多的控制权来控制它们如何被更改。但是在局部变量范围内,我们通常不需要这种控制,所以应首选可变集合。特别是在工具类中,元素的插入可能会发生很多次。 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Effective-Kotlin-zh-CN 2 | 3 | Effective Kotlin 中文翻译 4 | 5 | [在线阅读地址](https://maxzmeng.github.io/Effective-Kotlin-zh-CN/index.html) 6 | 7 | #### 当前进度:中文翻译中 8 | - [x] 英文原文搬运 9 | - [ ] 中文翻译 10 | - [ ] 校对 11 | 12 | 13 | 14 | 15 | - Part 1 Good Code 16 | - Chapter 1 Safety 17 | - [x] 引言 18 | - [x] 第1条:限制可变性 19 | - [x] 第2条:最小化变量作用域 20 | - [x] 第3条:尽快消除平台类型 21 | - [x] 第4条:不要把推断类型暴露给外部 22 | - [x] 第5条:在参数与状态上指定你的期望 23 | - [x] 第6条:尽可能使用标准库中提供的异常 24 | - [x] 第7条:当不能返回预期结果时,优先使用`null` o或`Failure` 作为返回值 25 | - [x] 第8条:正确地处理`null`值 26 | - [x] 第9条:使用`use`关闭资源 27 | - [x] 第10条:编写单元测试 28 | - Chapter 2 Readability 29 | - [x] 引言 30 | - [x] 第11条:可读性设计 31 | - [x] 第12条:操作符的含义应与其函数名一致 32 | - [x] 第13条:避免返回或操作 `Unit?` 33 | - [x] 第14条:在类型不明确的情况下,请显式指定变量的类型 34 | - [x] 第15条:考虑明确指定接收者 35 | - [x] 第16条:属性应代表状态,而不是行为 36 | - [x] 第17条:考虑命名参数 37 | - [x] 第18条:尊重编码规范 38 | - Part 2 Code Design 39 | - Chapter 3 Reusability 40 | - [ ] Introduction 41 | - [ ] Item 19 Do Not Repeat Knowledge 42 | - [ ] Item 20 Do Not Repeat Common Algorithms 43 | - [ ] Item 21 Use Property Delegation To Extract Common Property Patterns 44 | - [ ] Item 22 Use Generics When Implementing Common Algorithms 45 | - [ ] Item 23 Avoid Shadowing Type Parameters 46 | - [ ] Item 24 Consider Variance For Generic Types 47 | - [ ] Item 25 Reuse Between Different Platforms By Extracting Common Modules 48 | - Chapter 4 Abstraction Design 49 | - [ ] Introduction 50 | - [ ] Item 26 Each Function Should Be Written In Terms Of A Single Level Of Abstraction 51 | - [ ] Item 27 Use Abstraction To Protect Code Against Changes 52 | - [ ] Item 28 Specify API Stability 53 | - [ ] Item 29 Consider Wrapping External API 54 | - [ ] Item 30 Minimize Elements Visibility 55 | - [ ] Item 31 Define Contract With Documentation 56 | - [ ] Item 32 Respect Abstraction Contracts 57 | - Chapter 5 Object Creation 58 | - [ ] Introduction 59 | - [ ] Item 33 Consider Factory Functions Instead Of Constructors 60 | - [ ] Item 34 Consider A Primary Constructor With Named Optional Arguments 61 | - [ ] Item 35 Consider Defining A DSL For Complex Object Creation 62 | - Chapter 6 Class Design 63 | - [ ] Introduction 64 | - [ ] Item 36 Prefer Composition Over Inheritance 65 | - [ ] Item 37 Use The Data Modifier To Represent A Bundle Of Data 66 | - [ ] Item 38 Use Function Types Instead Of Interfaces To Pass Operations And Actions 67 | - [ ] Item 39 Prefer Class Hierarchies To Tagged Classes 68 | - [ ] Item 40 Respect The Contract Of Equals 69 | - [ ] Item 41 Respect The Contract Of Hash Code 70 | - [ ] Item 42 Respect The Contract Of Compare To 71 | - [ ] Item 43 Consider Extracting Non Essential Parts Of Your API Into Extensions 72 | - [ ] Item 44 Avoid Member Extensions 73 | - Part 3 Efficiency 74 | - Chapter 7 Make It Cheap 75 | - [ ] Introduction 76 | - [ ] Item 45 Avoid Unnecessary Object Creation 77 | - [ ] Item 46 Use Inline Modifier For Functions With Parameters Of Functional Types 78 | - [ ] Item 47 Consider Using Inline Classes 79 | - [ ] Item 48 Eliminate Obsolete Object References 80 | - Chapter 8 Efficient Collection Processing 81 | - [ ] Introduction 82 | - [ ] Item 49 Prefer Sequence For Big Collections With More Than One Processing Step 83 | - [x] 第50条:减少操作的次数 84 | - [x] 第51条:在“性能优先”的场景,使用基础类型数组 85 | - [x] 第52条:在处理局部变量时,考虑使用可变集合 86 | 87 | -------------------------------------------------------------------------------- /assets/chapter1/chapter1-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxzMeng/Effective-Kotlin-zh-CN/750403009f684b89fd04c2a07bbc946516d2de18/assets/chapter1/chapter1-1.png -------------------------------------------------------------------------------- /assets/chapter1/chapter1-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxzMeng/Effective-Kotlin-zh-CN/750403009f684b89fd04c2a07bbc946516d2de18/assets/chapter1/chapter1-2.png -------------------------------------------------------------------------------- /assets/chapter1/chapter1-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxzMeng/Effective-Kotlin-zh-CN/750403009f684b89fd04c2a07bbc946516d2de18/assets/chapter1/chapter1-3.png -------------------------------------------------------------------------------- /assets/chapter2/chapter2-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxzMeng/Effective-Kotlin-zh-CN/750403009f684b89fd04c2a07bbc946516d2de18/assets/chapter2/chapter2-1.png -------------------------------------------------------------------------------- /assets/chapter2/chapter2-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxzMeng/Effective-Kotlin-zh-CN/750403009f684b89fd04c2a07bbc946516d2de18/assets/chapter2/chapter2-2.png -------------------------------------------------------------------------------- /assets/chapter3/chapter3-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxzMeng/Effective-Kotlin-zh-CN/750403009f684b89fd04c2a07bbc946516d2de18/assets/chapter3/chapter3-1.png -------------------------------------------------------------------------------- /assets/chapter3/chapter3-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxzMeng/Effective-Kotlin-zh-CN/750403009f684b89fd04c2a07bbc946516d2de18/assets/chapter3/chapter3-2.png -------------------------------------------------------------------------------- /assets/chapter3/chapter3-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxzMeng/Effective-Kotlin-zh-CN/750403009f684b89fd04c2a07bbc946516d2de18/assets/chapter3/chapter3-3.png -------------------------------------------------------------------------------- /assets/chapter3/chapter3-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxzMeng/Effective-Kotlin-zh-CN/750403009f684b89fd04c2a07bbc946516d2de18/assets/chapter3/chapter3-4.png -------------------------------------------------------------------------------- /assets/chapter3/chapter3-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxzMeng/Effective-Kotlin-zh-CN/750403009f684b89fd04c2a07bbc946516d2de18/assets/chapter3/chapter3-5.png -------------------------------------------------------------------------------- /assets/chapter3/chapter3-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxzMeng/Effective-Kotlin-zh-CN/750403009f684b89fd04c2a07bbc946516d2de18/assets/chapter3/chapter3-6.png -------------------------------------------------------------------------------- /assets/chapter3/chapter3-7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxzMeng/Effective-Kotlin-zh-CN/750403009f684b89fd04c2a07bbc946516d2de18/assets/chapter3/chapter3-7.png -------------------------------------------------------------------------------- /assets/chapter3/chapter3-8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxzMeng/Effective-Kotlin-zh-CN/750403009f684b89fd04c2a07bbc946516d2de18/assets/chapter3/chapter3-8.png -------------------------------------------------------------------------------- /assets/chapter3/chapter3-9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxzMeng/Effective-Kotlin-zh-CN/750403009f684b89fd04c2a07bbc946516d2de18/assets/chapter3/chapter3-9.png -------------------------------------------------------------------------------- /assets/chapter4/chapter4-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxzMeng/Effective-Kotlin-zh-CN/750403009f684b89fd04c2a07bbc946516d2de18/assets/chapter4/chapter4-1.png -------------------------------------------------------------------------------- /assets/chapter4/chapter4-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxzMeng/Effective-Kotlin-zh-CN/750403009f684b89fd04c2a07bbc946516d2de18/assets/chapter4/chapter4-2.png -------------------------------------------------------------------------------- /assets/chapter4/chapter4-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxzMeng/Effective-Kotlin-zh-CN/750403009f684b89fd04c2a07bbc946516d2de18/assets/chapter4/chapter4-3.png -------------------------------------------------------------------------------- /assets/chapter4/chapter4-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxzMeng/Effective-Kotlin-zh-CN/750403009f684b89fd04c2a07bbc946516d2de18/assets/chapter4/chapter4-4.png -------------------------------------------------------------------------------- /assets/chapter4/chapter4-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxzMeng/Effective-Kotlin-zh-CN/750403009f684b89fd04c2a07bbc946516d2de18/assets/chapter4/chapter4-5.png -------------------------------------------------------------------------------- /assets/chapter4/chapter4-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxzMeng/Effective-Kotlin-zh-CN/750403009f684b89fd04c2a07bbc946516d2de18/assets/chapter4/chapter4-6.png -------------------------------------------------------------------------------- /assets/chapter4/chapter4-7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxzMeng/Effective-Kotlin-zh-CN/750403009f684b89fd04c2a07bbc946516d2de18/assets/chapter4/chapter4-7.png -------------------------------------------------------------------------------- /assets/chapter4/chapter4-8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxzMeng/Effective-Kotlin-zh-CN/750403009f684b89fd04c2a07bbc946516d2de18/assets/chapter4/chapter4-8.png -------------------------------------------------------------------------------- /assets/chapter4/chapter4-9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxzMeng/Effective-Kotlin-zh-CN/750403009f684b89fd04c2a07bbc946516d2de18/assets/chapter4/chapter4-9.png -------------------------------------------------------------------------------- /assets/chapter5/chapter5-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxzMeng/Effective-Kotlin-zh-CN/750403009f684b89fd04c2a07bbc946516d2de18/assets/chapter5/chapter5-1.png -------------------------------------------------------------------------------- /assets/chapter5/chapter5-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxzMeng/Effective-Kotlin-zh-CN/750403009f684b89fd04c2a07bbc946516d2de18/assets/chapter5/chapter5-2.png -------------------------------------------------------------------------------- /assets/chapter5/chapter5-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxzMeng/Effective-Kotlin-zh-CN/750403009f684b89fd04c2a07bbc946516d2de18/assets/chapter5/chapter5-3.png -------------------------------------------------------------------------------- /assets/chapter6/chapter6-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxzMeng/Effective-Kotlin-zh-CN/750403009f684b89fd04c2a07bbc946516d2de18/assets/chapter6/chapter6-1.png -------------------------------------------------------------------------------- /assets/chapter6/chapter6-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxzMeng/Effective-Kotlin-zh-CN/750403009f684b89fd04c2a07bbc946516d2de18/assets/chapter6/chapter6-2.png -------------------------------------------------------------------------------- /assets/chapter6/chapter6-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxzMeng/Effective-Kotlin-zh-CN/750403009f684b89fd04c2a07bbc946516d2de18/assets/chapter6/chapter6-3.png -------------------------------------------------------------------------------- /assets/chapter6/chapter6-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxzMeng/Effective-Kotlin-zh-CN/750403009f684b89fd04c2a07bbc946516d2de18/assets/chapter6/chapter6-4.png -------------------------------------------------------------------------------- /assets/chapter6/chapter6-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxzMeng/Effective-Kotlin-zh-CN/750403009f684b89fd04c2a07bbc946516d2de18/assets/chapter6/chapter6-5.png -------------------------------------------------------------------------------- /assets/chapter7/chapter7-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxzMeng/Effective-Kotlin-zh-CN/750403009f684b89fd04c2a07bbc946516d2de18/assets/chapter7/chapter7-1.png -------------------------------------------------------------------------------- /assets/chapter7/chapter7-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxzMeng/Effective-Kotlin-zh-CN/750403009f684b89fd04c2a07bbc946516d2de18/assets/chapter7/chapter7-2.png -------------------------------------------------------------------------------- /assets/chapter8/chapter8-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxzMeng/Effective-Kotlin-zh-CN/750403009f684b89fd04c2a07bbc946516d2de18/assets/chapter8/chapter8-1.png -------------------------------------------------------------------------------- /assets/chapter8/chapter8-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxzMeng/Effective-Kotlin-zh-CN/750403009f684b89fd04c2a07bbc946516d2de18/assets/chapter8/chapter8-2.png -------------------------------------------------------------------------------- /assets/chapter8/chapter8-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxzMeng/Effective-Kotlin-zh-CN/750403009f684b89fd04c2a07bbc946516d2de18/assets/chapter8/chapter8-3.png -------------------------------------------------------------------------------- /assets/chapter8/chapter8-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxzMeng/Effective-Kotlin-zh-CN/750403009f684b89fd04c2a07bbc946516d2de18/assets/chapter8/chapter8-4.png -------------------------------------------------------------------------------- /assets/chapter8/chapter8-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxzMeng/Effective-Kotlin-zh-CN/750403009f684b89fd04c2a07bbc946516d2de18/assets/chapter8/chapter8-5.png -------------------------------------------------------------------------------- /assets/chapter8/chapter8-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxzMeng/Effective-Kotlin-zh-CN/750403009f684b89fd04c2a07bbc946516d2de18/assets/chapter8/chapter8-6.png -------------------------------------------------------------------------------- /assets/chapter8/chapter8-7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxzMeng/Effective-Kotlin-zh-CN/750403009f684b89fd04c2a07bbc946516d2de18/assets/chapter8/chapter8-7.png -------------------------------------------------------------------------------- /docs/.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: auto-generate-gitbook 2 | on: 3 | push: 4 | branches: 5 | - main 6 | 7 | jobs: 8 | main-to-gh-pages: 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | - name: checkout main 13 | uses: actions/checkout@v2 14 | with: 15 | ref: main 16 | 17 | - name: install nodejs 18 | uses: actions/setup-node@v1 19 | 20 | - name: configue gitbook 21 | run: | 22 | npm install -g gitbook-cli 23 | gitbook install 24 | 25 | - name: build book 26 | run: | 27 | gitbook build . docs 28 | 29 | - name: publish book 30 | env: 31 | TOKEN: ${{ secrets.TOKEN }} 32 | REF: github.com/${{github.repository}} 33 | MYEMAIL: MaxzMeng@gmail.com 34 | MYNAME: ${{github.repository_owner}} 35 | run: | 36 | git config --global user.email "${MYEMAIL}" 37 | git config --global user.name "${MYNAME}" 38 | git init 39 | git add . 40 | git commit -m "Updated By Github Actions With Build ${{github.run_number}} of ${{github.workflow}} For Github Pages" 41 | git push 42 | -------------------------------------------------------------------------------- /docs/Part 1 Good code/Chapter 1 Safety/Introduction.md: -------------------------------------------------------------------------------- 1 | 是什么让我们决定在我们的项目中使用 Kotlin 而不是 Java、JavaScript 或 C++? 对于开发人员来说,他们经常被Kotlin简洁或惊人的特性所吸引。 而对于商用层面,我发现Kotlin 安全性是吸引大家使用它的强有力的支撑——它的设计消除了潜在的应用程序错误。 当您使用的应用程序崩溃时,或者当您花了一个小时把物品添加到购物车后网站出现错误从而导致无法结帐时,您即使是一名普通的用户都会感到十分懊恼。 更少的崩溃使用户和开发人员的生活变得更好,并提供了重要的商业价值。 2 | 3 | 安全性对我们来说很重要,Kotlin 是一种真正安全的语言,但它仍然需要开发人员的支持才能保证真正的安全。在本章中,我们将讨论 Kotlin 中最重要的安全性的最佳实践。我们将看到 Kotlin 本身的特性是如何提高安全性的,以及我们如何正确使用它们。本章中每一项的目的都是为了生成不易出错的代码。 -------------------------------------------------------------------------------- /docs/Part 1 Good code/Chapter 1 Safety/Item 10 Write unit tests.md: -------------------------------------------------------------------------------- 1 | ## 第10条:编写单元测试 2 | 3 | 在这一章中,你已经了解了许多使代码更安全的方法,但实现这一目标的最终方式是使用不同类型的测试。其中一种类型是检查应用程序从用户角度的行为是否正确。这种类型的测试通常是管理层唯一认可的,因为他们的主要目标是确保应用程序在外部行为正确。这些测试甚至不需要开发人员参与,而是可以由足够数量的测试人员处理,或者更好的选择是由测试工程师编写的自动化测试。 4 | 5 | 这些测试对于程序员来说很有用,但它们还不足够。它们不能提供对系统具体元素行为正确性的充分保证。它们也不能提供在开发过程中有用的快速反馈。为了实现这一点,我们需要一种不同类型的测试,对开发人员来说更加有用,由开发人员编写:单元测试。以下是一个示例单元测试,检查我们的 `fib` 函数在前5个位置上计算斐波那契数是否给出正确的结果: 6 | 7 | ``` kotlin 8 | @Test 9 | fun `fib works correctly for the first 5 positions`() { 10 | assertEquals(1, fib(0)) 11 | assertEquals(1, fib(1)) 12 | assertEquals(2, fib(2)) 13 | assertEquals(3, fib(3)) 14 | assertEquals(5, fib(4)) 15 | } 16 | ``` 17 | 18 | 使用单元测试时,我们通常会检查以下内容: 19 | 20 | - 常见用例(正常流程):检查我们预期元素被使用的典型方式。就像上面的示例中一样,我们测试函数在一些小数字上的工作情况。 21 | - 常见错误情况或潜在问题:测试我们认为可能无法正常工作或在过去显示存在问题的情况。 22 | - 边缘情况和非法参数:例如,对于 `Int` 类型,我们可以检查非常大的数字,如 `Int.MAX_VALUE`。对于可为空的对象,可能是 `null` 或填充了 `null` 值的对象。斐波那契数列中没有负数位置的数字,因此我们可以检查该函数在这种情况下的行为。 23 | 24 | 单元测试在开发过程中非常有用,因为它们可以快速反馈我们实现的元素如何工作。测试会不断累积,因此您可以轻松检查是否存在回归。它们还可以检查手动测试难以覆盖的情况。甚至有一种称为测试驱动开发(Test Driven Development,TDD)的方法,我们先编写一个单元测试,然后再编写实现以满足该测试。 25 | 26 | 单元测试带来的最大优势包括: 27 | 28 | 1. 可靠性提升:经过充分测试的元素通常更可靠。同时,心理上也会有安全感。当元素经过充分测试时,我们在操作它们时更有信心。 29 | 30 | 1. 重构的便利性:当元素经过适当的测试时,我们不再害怕对其进行重构。因此,经过充分测试的程序往往会逐渐改善。相比之下,没有测试的程序会让开发人员不敢轻易触碰遗留代码,因为他们可能会无意中引入错误。 31 | 32 | 1. 快速反馈循环:使用单元测试来检查元素是否正确工作通常比手动验证更快。更快的反馈循环加快了开发速度,使其更加愉快。它还有助于降低修复错误的成本:发现错误越早,修复成本越低。 33 | 34 | 当然,单元测试也存在一些缺点: 35 | 36 | 1. 编写单元测试需要时间。尽管从长远来看,良好的单元测试可以节省我们的时间,因为我们在后续的调试和查找错误过程中花费的时间更少。运行单元测试也比手动测试或其他类型的自动化测试快很多,从而节省了大量时间。 37 | 38 | 1. 需要调整代码以使其具备可测试性。这些更改通常很困难,但它们通常也迫使开发人员使用良好和成熟的架构。 39 | 40 | 1. 编写好的单元测试很困难。这需要一定的技能和理解,这些技能与其他开发工作有所不同。编写质量低劣的单元测试可能会带来负面影响。每个人都需要学习如何正确地为自己的代码编写单元测试,最好先参加软件测试或测试驱动开发(TDD)的课程。 41 | 42 | 最大的挑战是获得有效进行单元测试并编写支持单元测试的代码的技能。经验丰富的 Kotlin 开发人员应该掌握这些技能,并至少对代码的重要部分进行单元测试,包括: 43 | 44 | - 复杂功能 45 | - 预计会随时间变化或进行重构的部分 46 | - 业务逻辑 47 | - 公共 API 的部分 48 | - 容易出现问题的部分 49 | - 我们修复的生产错误 50 | 51 | 我们也不需要止步于此。测试是对应用程序可靠性和长期可维护性的一种投资。 52 | 53 | ### 总结 54 | 55 | 本章以一个反思开始,即我们程序的首要任务应该是正确地运行。通过使用本章介绍的良好实践可以支持这一点,但最重要的是,确保我们的应用程序正确运行的最佳方法是通过测试来检查,尤其是单元测试。这就是为什么一个负责任的关于安全性的章节至少需要包含一个关于单元测试的简短部分的原因。就像负责任的商业应用程序需要至少一些单元测试一样。 -------------------------------------------------------------------------------- /docs/Part 1 Good code/Chapter 1 Safety/Item 2 Minimize the scope of variables.md: -------------------------------------------------------------------------------- 1 | ## 第2条:最小化变量作用域 2 | 3 | 当我们定义一个状态时,我们倾向于通过以下方式收紧变量和属性的范围: 4 | 5 | - 使用局部变量代替属性 6 | - 在尽可能小的范围内使用变量,例如,如果一个变量只在循环中使用,那么就在这个循环中定义它 7 | 8 | 元素的作用域是指计算机程序中该元素可见的区域。在Kotlin中,作用域几乎总是由花括号创建的,我们通常可以从作用域内部访问外部的元素。看看这个例子: 9 | 10 | ``` kotlin 11 | val a = 1 12 | fun fizz() { 13 | val b = 2 14 | print(a + b) 15 | } 16 | val buzz = { 17 | val c = 3 18 | print(a + c) 19 | } 20 | // 这里可以访问到 a ,但是无法访问 b 或 c 21 | ``` 22 | 23 | 在上面的例子中,在函数` fizz `和` buzz `的作用域中,我们可以从函数作用域中访问外部作用域的变量。但是,在外部作用域中,我们不能访问这些函数中定义的变量。下面是一个限制变量作用域的示例: 24 | 25 | ``` kotlin 26 | // 不好的写法 27 | var user: User 28 | for (i in users.indices) { 29 | user = users[i] 30 | print("User at $i is $user") 31 | } 32 | 33 | // 较好的写法 34 | for (i in users.indices) { 35 | val user = users[i] 36 | print("User at $i is $user") 37 | } 38 | 39 | // 相同的变量作用域,更好的语法 40 | for ((i, user) in users.withIndex()) { 41 | print("User at $i is $user") 42 | } 43 | ``` 44 | 45 | 在第一个例子中,` user ` 变量不仅在for循环的范围内可以访问,而且在for循环之外也可以访问。在第二个和第三个例子中,我们将变量` user ` 的作用域具体限制为for循环的作用域。 46 | 47 | 类似地,在作用域中可能还有许多作用域(最有可能的是嵌套在lambda表达式中的lambda表达式创建的),最好在尽可能小的范围内定义变量。 48 | 49 | 我们这么做原因有很多,但最重要的是:**当我们收紧变量的作用域时,就会使我们的程序易于调试和管理。**当我们分析代码时,我们需要考虑此时存在哪些元素。需要处理的元素越多,编程就越困难。应用程序越简单,它崩溃的可能性就越小。这也是为什么我们更喜欢不可变属性或对象。 50 | 51 | **考虑可变属性,当它们只能在较小的范围内修改时,更容易跟踪它们如何更改。**更容易对他们进行推理并改变他们的行为。 52 | 53 | 另一个问题是,**具有更大范围的变量可能会被其他开发人员过度使用**。例如,有人可能认为,如果使用一个变量来为迭代中的下一个元素赋值,那么在循环完成后,列表中的最后一个元素应该保留在该变量中。这样的推理可能导致严重的滥用,比如在迭代之后使用这个变量对最后一个元素做一些事情。这将是非常糟糕的,因为当另一个开发人员试图理解这个值的含义时,就需要理解整个执行过程。这将是一个不必要的麻烦。 54 | 55 | **无论变量是只读的还是可读写的,我们总是倾向于在定义变量时就对其进行初始化。**不要强迫开发人员查看它的定义位置。这可以通过控制结构语句来实现,例如if, when, try-catch或Elvis操作符用作表达式: 56 | 57 | ``` kotlin 58 | // 不好的写法 59 | val user: User 60 | if (hasValue) { 61 | user = getValue() 62 | } else { 63 | user = User() 64 | } 65 | 66 | // 较好的写法 67 | val user: User = if(hasValue) { 68 | getValue() 69 | } else { 70 | User() 71 | } 72 | ``` 73 | 74 | 如果我们需要设置多个属性,解构声明可以帮助我们更好的实现: 75 | 76 | ``` kotlin 77 | // 不好的写法 78 | fun updateWeather(degrees: Int) { 79 | val description: String 80 | val color: Int 81 | if (degrees < 5) { 82 | description = "cold" 83 | color = Color.BLUE 84 | } else if (degrees < 23) { 85 | description = "mild" 86 | color = Color.YELLOW 87 | } else { 88 | description = "hot" 89 | color = Color.RED 90 | } 91 | // ... 92 | } 93 | 94 | // 较好的写法 95 | fun updateWeather(degrees: Int) { 96 | val (description, color) = when { 97 | degrees < 5 -> "cold" to Color.BLUE 98 | degrees < 23 -> "mild" to Color.YELLOW 99 | else -> "hot" to Color.RED 100 | } 101 | // ... 102 | } 103 | ``` 104 | 105 | 最后,太大的变量范围可能是危险的。让我们来看一个例子。 106 | 107 | ### 变量捕获 108 | 109 | 当我在教授 Kotlin 协程时,我布置的练习之一是使用序列构建器实现 Eratosthenes 算法以查找素数。 该算法在概念上很简单: 110 | 111 | 1. 创建一个从 2 开始的数字列表。 112 | 2. 取第一个数,它是一个素数。 113 | 3. 从其余的数字中,我们删除第一个数字,并过滤掉所有可以被这个素数整除的数字。 114 | 115 | 该算法的一个简单实现如下所示: 116 | 117 | ``` kotlin 118 | var numbers = (2..100).toList() 119 | val primes = mutableListOf() 120 | while (numbers.isNotEmpty()) { 121 | val prime = numbers.first() 122 | primes.add(prime) 123 | numbers = numbers.filter { it % prime != 0 } 124 | } 125 | print(primes) // [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 126 | // 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97] 127 | ``` 128 | 129 | 这个问题的挑战在于如何让它产生一个可能无限的质数序列。 如果您想挑战自己,请立即停止阅读并尝试实现它。 130 | 131 | 这是一种解决方法: 132 | 133 | ``` kotlin 134 | val primes: Sequence = sequence { 135 | var numbers = generateSequence(2) { it + 1 } 136 | 137 | while (true) { 138 | val prime = numbers.first() 139 | yield(prime) 140 | numbers = numbers.drop(1) 141 | .filter { it % prime != 0 } 142 | } 143 | } 144 | 145 | print(primes.take(10).toList()) 146 | // [2, 3, 5, 7, 11, 13, 17, 19, 23, 29] 147 | ``` 148 | 149 | 在几乎每一组中都有一个人试图“优化”它。他们提取```prime```作为可变变量,而不是在每个循环中都创建变量: 150 | 151 | ``` kotlin 152 | val primes: Sequence = sequence { 153 | var numbers = generateSequence(2) { it + 1 } 154 | 155 | var prime: Int 156 | while (true) { 157 | prime = numbers.first() 158 | yield(prime) 159 | numbers = numbers.drop(1) 160 | .filter { it % prime != 0 } 161 | } 162 | } 163 | ``` 164 | 165 | 但是这会导致这个实现不再能得到正确的结果。以下是前10个数字: 166 | 167 | ``` kotlin 168 | print(primes.take(10).toList()) 169 | // [2, 3, 5, 6, 7, 8, 9, 10, 11, 12] 170 | ``` 171 | 172 | 现在你可以停下来去尝试解释为什么会出现这样的结果。 173 | 174 | 我们得到这样结果的原因是我们捕获了变量`prime`。因为我们使用的是序列,所以过滤是惰性完成的。在每一步中,我们不断地在添加过滤器。而在“优化”版本的代码中,我们总是只添加引用可变属性`prime`的过滤器。 因此,我们总是过滤` prime`的最后一个值用来过滤。 这就是为什么我们不能过滤出正确的结果。只有drop函数生效了,所以我们得到的是一个连续的数字序列 (除了`prime `被设置为2时被过滤掉的4). 175 | 176 | 我们应该意识到这种无意捕获的问题,因为这种情况时有发生。为了防止这种情况,我们应该避免可变性,并使变量的作用域更小。 177 | 178 | ### 总结 179 | 180 | 出于许多原因,我们应该更倾向于在最小的范围内定义变量。同样,对于局部变量,我们更喜欢` val` 而不是`var `。我们应该始终意识到变量是在lambdas表达式中被捕获的。这些简单的规则可以为我们省去许多麻烦。 181 | -------------------------------------------------------------------------------- /docs/Part 1 Good code/Chapter 1 Safety/Item 4 Do not expose inferred types.md: -------------------------------------------------------------------------------- 1 | ## 第4条:不要把推断类型暴露给外部 2 | 3 | Kotlin的类型推断是JVM中最流行的Kotlin特性之一。以至于Java 10也引入了类型推断(与Kotlin相比有限)。 不过,使用该特性也有一些危险性。最重要的是,我们需要记住,赋值的推断类型是右边的确切类型,而不是超类或接口类型: 4 | 5 | ```kotlin 6 | open class Animal 7 | class Zebra: Animal() 8 | 9 | fun main() { 10 | var animal = Zebra() 11 | animal = Animal() // Error: Type mismatch 12 | } 13 | ``` 14 | 15 | 在大多数情况下,这都不是一个难题。当我们需要限定推断出的类型时,我们只需要指定它,问题就解决了: 16 | 17 | ``` kotlin 18 | open class Animal 19 | class Zebra: Animal() 20 | 21 | fun main() { 22 | var animal: Animal = Zebra() 23 | animal = Animal() 24 | } 25 | ``` 26 | 27 | 然而,当我们使用三方编写的一个库或另一个模块时,就没有这么容易了。在这种情况下,推断类型说明可能非常危险。让我们看一个例子。 28 | 29 | 假设你有以下接口用来代表汽车工厂: 30 | 31 | ``` kotlin 32 | interface CarFactory { 33 | fun produce(): Car 34 | } 35 | ``` 36 | 37 | 如果没有指定其他参数,也会使用默认的类型`Fiat126P`: 38 | 39 | ``` kotlin 40 | val DEFAULT_CAR: Car = Fiat126P() 41 | ``` 42 | 43 | 因为绝大多数汽车工厂都可以生产它,所以你把它设为默认值。你没有给它声明返回值类型,因为你认为`DEFAULT_CAR `无论如何都是`Car`的实例: 44 | 45 | ``` kotlin 46 | interface CarFactory { 47 | fun produce() = DEFAULT_CAR 48 | } 49 | ``` 50 | 51 | 类似地, 后来有其他人看到 `DEFAULT_CAR`的声明并且认为它的类型能够被推断出来: 52 | 53 | ``` kotlin 54 | val DEFAULT_CAR = Fiat126P() 55 | ``` 56 | 57 | 现在你会发现所有的工厂都只能生产`Fiat126P`。这显然是有问题的。如果这个接口是你自己定义的,那么这个问题可能很快就会被发现并且很容易修复。但是,如果它作为外部API被提供给用户使用,你可能会从愤怒的用户那里得知这个问题。 58 | 59 | 除此之外,当某人不太了解API时,返回类型是很重要的信息。因此为了可读性,我们应该显式声明返回类型,特别是在我们的API的外部可见部分(即公开的API)中。 60 | 61 | #### 总结 62 | 63 | 一般的规则是,如果我们不确定返回值类型,我们应该显示声明它。这是很重要的信息,我们不应该把它隐藏起来 (*Item 14: Specify the variable type when it is not clear*)。此外,为了安全起见,在外部API中,我们应该始终指定类型。不能让它们随意改变。当我们的项目迭代时,推断类型可能会有很多限制或者很容易被更改。 64 | -------------------------------------------------------------------------------- /docs/Part 1 Good code/Chapter 1 Safety/Item 6 Prefer standard errors to custom ones.md: -------------------------------------------------------------------------------- 1 | ## 第6条:尽可能使用标准库中提供的异常 2 | 3 | `require`, `check` 和`assert`函数涵盖了Kotlin中最常见的异常情况, 但还是有很多其他情况需要我们去主动抛出异常。例如,当你实现一个库来解析JSON时,当提供的JSON文件格式不正确时时,抛出一个`JsonParsingException`是合理的: 4 | 5 | ``` kotlin 6 | inline fun String.readObject(): T { 7 | //... 8 | if (incorrectSign) { 9 | throw JsonParsingException() 10 | } 11 | //... 12 | return result 13 | } 14 | ``` 15 | 16 | 这里我们使用了一个自定义异常,因为在标准库中没有合适的异常来表答这种情况。你应该尽可能使用标准库提供的异常,而不是定义自己定义的异常。这些标准库提供的异常应该被开发者熟知和复用。在特定场景下使用这些异常会使你的API更容易学习和理解。下面是一些你可以使用的最常见的异常: 17 | 18 | - `IllegalArgumentException` 和`IllegalStateException` - 像在第5条中提到的那样,使用 `require` 和`check` 来抛出该异常。 19 | - `IndexOutOfBoundsException` - 表示索引越界。 常用于集合和数组。比如在 `ArrayList.get(Int)`中就会抛出该异常。 20 | - `ConcurrentModificationException` - 表示并发修改是禁止的,当检测到这种行为时会抛出该异常。 21 | - `UnsupportedOperationException` - 表示该对象不支持它声明的方法。我们应该避免这种情况,当一个方法不受支持时,它就不应该被声明在类中。 22 | - `NoSuchElementException` - 表示被请求的元素不存在。 例如,当我们实现`Iterable`时,迭代器内已经没有其他元素了但是还是调用了 `next` 方法。 -------------------------------------------------------------------------------- /docs/Part 1 Good code/Chapter 1 Safety/Item 7 Prefer null or Failure result when the lack of result is possible.md: -------------------------------------------------------------------------------- 1 | ## 第7条:当不能返回预期结果时,优先使用`null` o或`Failure` 作为返回值 2 | 3 | 有时,函数不能返回预期的结果。常见的例子有: 4 | 5 | - 我们试图从服务器获取数据,但我们的网络连接有问题 6 | - 我们试图获取符合某些条件的第一个元素,但在我们的集合中没有这样的元素 7 | - 我们试图把文本解析为一个对象,但是文本的格式是错误的 8 | 9 | 有两种主要的办法来处理这种情况: 10 | 11 | - 返回一个`null`或一个表示失败的密封类(通常命名为 `Failure`) 12 | - 抛出一个异常 13 | 14 | 这两者之间有一个重要的区别。异常不应该被用作传递信息的标准方式。**所有异常都是指不正确的、特殊的情况,应按这种方式处理。我们应该只在特殊情况下使用异常**(来自Joshua Bloch的Effective Java)**。**这样做的主要原因是: 15 | 16 | - 异常传递的方式对大多数程序员来说可读性较差,并且很容易在代码中漏掉。 17 | - 在Kotlin中,所有异常都未经检查。用户不会被强迫甚至鼓励去处理它们。它们通常没有很好地被标记出来。当我们使用API时,它们实际上是不可见的。 18 | - 因为异常是为异常环境设计的,所以JVM的实现者们没有任何动机让它们有和正常情况一样的执行速度。 19 | - 将代码放在try-catch块中会抑制编译器可能执行的某些优化。 20 | 21 | 另一方面,`null`或`Failure`都是表示预期错误的最佳选择。它们是显式的、高效的,并且可以用惯用的方式处理。这就是为什么**当发生预期内的错误时,我们应该更倾向于返回`null`或`Failure `时,而当发生预期外的错误时,应该更倾向于抛出一个异常。**以下是一些例子: 22 | 23 | ``` kotlin 24 | inline fun String.readObjectOrNull(): T? { 25 | //... 26 | if (incorrectSign) { 27 | return null 28 | } 29 | //... 30 | return result 31 | } 32 | 33 | inline fun String.readObject(): Result { 34 | //... 35 | if (incorrectSign) { 36 | return Failure(JsonParsingException()) 37 | } 38 | //... 39 | return Success(result) 40 | } 41 | 42 | sealed class Result 43 | class Success(val result: T) : Result() 44 | class Failure(val throwable: Throwable) : Result() 45 | 46 | class JsonParsingException : Exception() 47 | ``` 48 | 49 | 当我们选择返回`null`时,我们可以从各种支持空安全的特性中选择来处理这样的值的方式,比如安全调用或使用Elvis操作符: 50 | 51 | ``` kotlin 52 | val age = userText.readObjectOrNull()?.age ?: -1 53 | ``` 54 | 55 | 当我们选择返回像`Result`这样的复合类型时,用户将能够使用When表达式来处理它: 56 | 57 | ``` kotlin 58 | val personResult = userText.readObject() 59 | val age = when(personResult) { 60 | is Success -> personResult.value.age 61 | is Failure -> -1 62 | } 63 | ``` 64 | 65 | 使用这种错误处理不仅比try-catch块更有效,而且通常更容易使用,可读性更好。一个异常可能会被错过,并可能导致整个应用程序停止。虽然`null`值或密封的结果类需要显式处理,但它不会中断应用程序的流程。 66 | 67 | 当在`null`和`Failure`两者间进行选择时,如果失败时需要传递额外的信息,我们应该选择后者,否则应该选择`null`。`Failure`可以保存你需要的任何数据。 68 | 69 | 通常来说我们应该对一个函数提供两种变体——一种会抛出异常,另一种不会。一个很好的例子是`List`有以下两个方法: 70 | 71 | - `get` 返回给定下标位置的元素,如果对应的下标超出了列表范围, 这个方法会抛出`IndexOutOfBoundsException`. 72 | - `getOrNull`,当我们知道我们可能会访问一个超出列表范围的元素时,我们应该使用它,这样当下标超出范围时,它会返回给我们 `null`. 73 | 74 | 它还支持其他选项,如`getOrDefault`,这在某些情况下很有用,但通常可能很容易替换为`getOrNull`和Elvis操作符`?:`。 75 | 76 | 这是一个很好的实践,因为如果开发人员知道他们正在安全地获取一个元素,就不应该强迫他们处理一个可为空的值,同时,如果他们有任何不确定,他们应该使用`getOrNull `并正确地处理默认值。 -------------------------------------------------------------------------------- /docs/Part 1 Good code/Chapter 1 Safety/Item 9 Close resources with use.md: -------------------------------------------------------------------------------- 1 | ## 第9条:使用 `use`关闭资源 2 | 3 | 有些资源不能自动关闭,当我们不再使用它们时,我们需要调用`close`方法来手动关闭。我们在Kotlin/JVM中使用的Java标准库包含了很多这样的资源,例如 4 | 5 | - `InputStream` 和`OutputStream` 6 | - `java.sql.Connection`, 7 | - `java.io.Reader` (`FileReader`, `BufferedReader`, `CSSParser`) 8 | - `java.new.Socket` 和`java.util.Scanner` 9 | 10 | 所有这些资源都实现了`Closeable `接口,它继承自`AutoCloseable `。 11 | 12 | 对于上面列举的这些例子,当我们确认不再需要该资源的时候,我们需要调用`close`方法,因为这些资源的调用开销比较大并且它们被自动关闭的成本也较高(如果没有任何对该资源的引用,垃圾收集器最终会将它关闭,但这一过程所需的时间会比较久)。 因此,为了确保我们不会漏掉关闭它们,我们通常将这些资源调用放在在一个`try-finally`块中,并在`finally`中调用`close`方法: 13 | 14 | ``` kotlin 15 | fun countCharactersInFile(path: String): Int { 16 | val reader = BufferedReader(FileReader(path)) 17 | try { 18 | return reader.lineSequence().sumBy { it.length } 19 | } finally { 20 | reader.close() 21 | } 22 | } 23 | ``` 24 | 25 | 这样的结构是复杂且不正确的。它的不正确体现在`close`可能会抛出异常且这个异常不会被捕获。此外,如果我们同时从`try`和`finally`块中抛出异常,那么只有一个异常会被正确地传递。 我们所期望的表现应该是后抛出的异常信息应该被添加到之前已经抛出的异常信息中。正确的实现很长并且很复杂,但这种处理很常见,因此Kotlin标准库中提供了`use`函数。应该使用它来正确关闭资源和处理异常,此函数可用于任何`Closeable `对象: 26 | 27 | ``` kotlin 28 | fun countCharactersInFile(path: String): Int { 29 | val reader = BufferedReader(FileReader(path)) 30 | reader.use { 31 | return reader.lineSequence().sumBy { it.length } 32 | } 33 | } 34 | ``` 35 | 36 | 调用`use`的对象(本例中为`reader`)也会作为参数传递给lambda表达式,因此语法可以缩短: 37 | 38 | ``` kotlin 39 | fun countCharactersInFile(path: String): Int { 40 | BufferedReader(FileReader(path)).use { reader -> 41 | return reader.lineSequence().sumBy { it.length } 42 | } 43 | } 44 | ``` 45 | 46 | 因为`use`函数经常被用来操作文件,同时逐行读取文件也是一种很常见的操作,所以Kotlin标准库中提供了一个` useLines`函数,它会返回给我们一个包含了文件中每一行内容(`String`类型)的序列,并且在读取完毕之后会自动关闭文件资源: 47 | 48 | ``` kotlin 49 | fun countCharactersInFile(path: String): Int { 50 | File(path).useLines { lines -> 51 | return lines.sumBy { it.length } 52 | } 53 | } 54 | ``` 55 | 56 | 这是一种适合用来处理大文件的方法,因为序列会按需读取每一行,因此每次调用对于内存的占用不会超过一行的内容所对应的内存大小。 但代价是这个序列只能使用一次,如果你需要多次遍历文件,则需要多次调用它。 `useLines` 函数同样也能作为一个表达式来调用。 57 | 58 | ``` kotlin 59 | fun countCharactersInFile(path: String): Int = 60 | File(path).useLines { lines -> 61 | lines.sumBy { it.length } 62 | } 63 | ``` 64 | 65 | 以上所有使用序列对文件进行操作的例子,都是比较合理的处理方法。因为这样我们可以每次只加载一行的内容,避免直接加载整个文件。更多相关内容请参考 *Item 49: Prefer Sequence for big collections with more than one processing step*. 66 | 67 | ### Summary 68 | 69 | 使用`use`对实现了`Closeable`或`AutoCloseable`的对象进行操作,是一个安全且简单的选择。当你需要操作一个文件时,考虑使用`useLines`,它会生成一个序列来帮助你遍历每一行。 -------------------------------------------------------------------------------- /docs/Part 1 Good code/Chapter 2 Readability/Introduction.md: -------------------------------------------------------------------------------- 1 | # 第2章:可读性 2 | 3 | > "任何傻瓜都能编写计算机能理解的代码。优秀的程序员编写的是人类能理解的代码。" 4 | > –马丁·福勒,《重构:改善现有代码的设计》,第15页 5 | 6 | 有一个非常常见的误解,认为Kotlin的设计目标是简洁。实际上,并非如此。有些语言比Kotlin更简洁。例如,我所了解的最简洁的语言是APL。以下是用APL实现的约翰·康威的“生命游戏”(Game of Life): 7 | 8 | ![](../../assets/chapter2/chapter2-1.png) 9 | 10 | 你可能会惊叹于这段代码的简洁。然而,你可能会意识到你的键盘上没有其中一些字符。还有更多这样的语言,例如,下面是同样的程序用J语言实现的版本: 11 | 12 | 13 | 14 | ``` 15 | 1 life=:[:+/(3 4=/[:+/(,/,"0/~i:1)|.])*.1,:] 16 | ``` 17 | 18 | 这两种语言确实非常简洁。这使它们在代码高尔夫比赛中成为冠军。但这也使它们难以阅读。说实话,即使对于经验丰富的APL开发人员(全世界可能只有几个),理解这个程序的功能和工作原理也是一项挑战。 19 | 20 | Kotlin从来没有追求过极致的简洁性。它的设计目标是**可读性**。与其他流行语言相比,Kotlin确实更加简洁,但这是因为Kotlin消除了许多冗余代码和重复结构。这样做是为了帮助开发人员专注于重要的内容,从而使Kotlin更易读。 21 | 22 | Kotlin允许程序员设计干净且有意义的代码和API。其特性使我们能够隐藏或突出显示我们想要的内容。本章将介绍如何明智地使用这些工具。本章作为引言,提供了一些通用建议。虽然它也介绍了可读性的概念,我们将在本书的其余部分中继续涉及这个概念,尤其是在“第2部分:抽象设计”中,我们将深入探讨与类和函数设计相关的主题。 23 | 24 | 让我们从更抽象的关于可读性的主题开始,引入一般性的问题。 25 | -------------------------------------------------------------------------------- /docs/Part 1 Good code/Chapter 2 Readability/Item 11 Design for readability.md: -------------------------------------------------------------------------------- 1 | ## 第11条:可读性设计 2 | 3 | 在编程中,一个已知的观察结果是,开发人员阅读代码的时间远远超过编写代码的时间。一般估计是,写一分钟的代码需要阅读十分钟(这个比例在罗伯特·C·马丁的书《代码整洁之道》中得到普及)。如果你觉得这难以置信,想想你在查找错误时花费了多少时间来阅读代码。我相信每个人在自己的职业生涯中至少有一次遇到过这种情况,他们花了几个星期的时间寻找错误,最终只需改变一行代码就能修复它。当我们学习如何使用新的API时,通常是通过阅读代码来实现的。我们通常通过阅读代码来理解逻辑或实现的方式。**编程主要是关于阅读,而不是写作。**因此,我们应该以可读性为重点进行编码。 4 | 5 | ### 减少认知负荷 6 | 7 | 可读性对每个人来说意味着不同的事情。然而,有一些规则是基于经验形成的,或者来自认知科学。只需比较以下两种实现: 8 | 9 | ``` kotlin 10 | // Implementation A 11 | if (person != null && person.isAdult) { 12 | view.showPerson(person) 13 | } else { 14 | view.showError() 15 | } 16 | 17 | // Implementation B 18 | person?.takeIf { it.isAdult } 19 | ?.let(view::showPerson) 20 | ?: view.showError() 21 | ``` 22 | 23 | 哪个更好,A还是B?使用简单的推理,认为行数较少的更好,这不是一个好答案。我们可以从第一个实现中删除换行符,它不会变得更易读。 24 | 25 | 这两种结构的可读性取决于我们能多快地理解它们。而这又在很大程度上取决于我们的大脑对每个习惯用法(结构、函数、模式)的训练程度。对于Kotlin的初学者来说,显然实现A更易读。它使用了常见的习惯用法(if/else,`&&`,方法调用)。实现B使用了Kotlin特有的习惯用法(安全调用`?.`,`takeIf`,`let`,Elvis运算符`?:`,有界函数引用`view::showPerson`)。当然,所有这些习惯用法在Kotlin中广泛使用,所以大多数有经验的Kotlin开发人员都熟悉它们。然而,很难进行比较。Kotlin并不是大多数开发人员的第一门语言,我们在一般编程方面拥有更多的经验而不是Kotlin编程方面的经验。我们编写的代码不仅是为有经验的开发人员编写的。你雇佣的初级开发人员(在经过数月的寻找高级开发人员无果之后)很可能不知道`let`、`takeIf`和有界引用是什么。他们很可能从未见过以这种方式使用Elvis运算符。这个人可能会花一整天来琢磨这个代码块。此外,即使对于有经验的Kotlin开发人员来说,他们也不只使用Kotlin这一种编程语言。让大脑识别Kotlin特定的习惯用法总是需要一些时间。即使在使用Kotlin多年之后,我仍然能够更快地理解第一个实现。每个不太常见的习惯用法都会引入一些复杂性,当我们在需要几乎同时理解所有这些习惯用法的单个语言块时,这种复杂性会累积起来。 26 | 27 | 请注意,实现方式A更容易修改。假设我们需要在if块中添加额外的操作。在实现方式A中,这样的添加很容易。在实现方式B中,我们不能再使用函数引用。在实现方式B的else块中添加内容更加困难 - 我们需要使用某个函数来能够在Elvis运算符的右侧容纳多个表达式。 28 | 29 | ``` kotlin 30 | if (person != null && person.isAdult) { 31 | view.showPerson(person) 32 | view.hideProgressWithSuccess() 33 | } else { 34 | view.showError() 35 | view.hideProgress() 36 | } 37 | 38 | person?.takeIf { it.isAdult } 39 | ?.let { 40 | view.showPerson(it) 41 | view.hideProgressWithSuccess() 42 | } ?: run { 43 | view.showError() 44 | view.hideProgress() 45 | } 46 | ``` 47 | 48 | 调试实现方式A也要简单得多。难怪如此 - 调试工具是为这种基本结构而设计的。 49 | 50 | 一般规则是,不太常见和"创造性"的结构通常不太灵活,支持也不那么好。举个例子,假设我们需要添加第三个分支,在person为null时显示不同的错误,当person不是成年人时显示另一种错误。在实现方式A中,我们可以轻松地将if/else更改为when,使用IntelliJ的重构功能,然后轻松地添加额外的分支。而在实现方式B中,同样的更改将非常困难,可能需要完全重写。 51 | 52 | 你有没有注意到实现方式A和B甚至没有以相同的方式工作?你能看出区别吗?现在回过头来思考一下。 53 | 54 | 区别在于let从lambda表达式中返回一个结果。这意味着如果showPerson返回null,那么第二个实现方式也会调用showError!这显然不明显,它告诉我们,当我们使用不太熟悉的结构时,更容易遇到意外行为(并且很难发现)。 55 | 56 | 这里的一般规则是我们要减少认知负荷。我们的大脑识别模式,并基于这些模式构建我们对程序如何工作的理解。当我们考虑可读性时,我们希望缩短这个距离。我们更喜欢更少的代码,但也更常见的结构。当我们经常看到熟悉的模式时,我们会识别出它们。我们总是更喜欢在其他学科中熟悉的结构。 57 | 58 | ### 不要过于极端 59 | 60 | 仅仅因为在之前的例子中我演示了`let`的误用,并不意味着它应该被完全避免。`let`是一种常见的习惯用法,在各种情境下合理使用可以改善代码。一个常见的例子是当我们有一个可为空的可变属性,并且只有在它不为null时才执行某个操作。由于可变属性可能会被其他线程修改,因此无法使用智能转换。解决这个问题的一种很好的方法是使用安全调用`let`: 61 | 62 | ``` kotlin 63 | class Person(val name: String) 64 | var person: Person? = null 65 | 66 | fun printName() { 67 | person?.let { 68 | print(it.name) 69 | } 70 | } 71 | ``` 72 | 73 | 这样的习惯用法是广为人知且使用广泛的。`let`还有很多其他合理的用例。例如: 74 | 75 | - 将操作移到其参数计算之后 76 | - 使用`let`将对象包装为装饰器 77 | 78 | 以下是展示这两种用例的示例(还使用了函数引用): 79 | 80 | ``` kotlin 81 | students 82 | .filter { it.result >= 50 } 83 | .joinToString(separator = "\n") { 84 | "${it.name} ${it.surname}, ${it.result}" 85 | } 86 | .let(::print) 87 | 88 | var obj = FileInputStream("/file.gz") 89 | .let(::BufferedInputStream) 90 | .let(::ZipInputStream) 91 | .let(::ObjectInputStream) 92 | .readObject() as SomeObject 93 | ``` 94 | 95 | 在所有这些情况下,我们付出了一定的代价 - 这段代码更难调试,对于经验较少的Kotlin开发人员来说理解起来也更困难。但我们为此付出了代价,而且似乎是一个公平的交易。问题在于当我们为没有充分理由引入大量复杂性时。 96 | 97 | 对于某些事物是否合理将始终存在争议。在这方面取得平衡是一门艺术。然而,认识到不同的结构引入了复杂性或者使事情更加清晰是很重要的。尤其是当它们一起使用时,两种结构的复杂性通常远远超过它们各自复杂性的总和。 98 | 99 | ### 约定俗成 100 | 101 | 我们承认不同的人对可读性有不同的看法。我们经常在函数命名上争论不休,讨论什么应该是显式的,什么是隐式的,应该使用什么习语等等。编程是一门表达能力的艺术。然而,也有一些需要理解和记住的约定俗成。 102 | 103 | 当我在旧金山的一个研讨会上的一个小组问我在Kotlin中最糟糕的事情是什么时,我给了他们这个例子: 104 | 105 | ``` kotlin 106 | val abc = "A" { "B" } and "C" 107 | print(abc) // ABC 108 | ``` 109 | 110 | 我们只需要以下代码就能实现这种可怕的语法: 111 | 112 | ``` kotlin 113 | operator fun String.invoke(f: ()->String): String = 114 | this + f() 115 | 116 | infix fun String.and(s: String) = this + s 117 | ``` 118 | 119 | 这段代码违反了我们之后将要描述的许多规则: 120 | 121 | - 它违反了操作符的含义 - `invoke`不应该以这种方式使用。一个字符串不能被调用。 122 | - 在这里使用“lambda作为最后一个参数”的约定会令人困惑。在函数之后使用它是可以的,但是当我们在invoke操作符上使用它时要非常小心。 123 | - `and`显然是这个中缀方法的一个糟糕的名称。`append`或`plus`会好得多。 124 | - 我们已经有了用于字符串连接的语言特性,应该使用它们而不是重复造轮子。 125 | 126 | 127 | 在每个建议背后,都有一条更普遍的规则来保护良好的Kotlin风格。在本章中,我们将介绍最重要的规则,从重写操作符开始讨论。 -------------------------------------------------------------------------------- /docs/Part 1 Good code/Chapter 2 Readability/Item 12 Operator meaning should be consistent with its function name.md: -------------------------------------------------------------------------------- 1 | ## 第12条:操作符的含义应与其函数名一致 2 | 3 | 操作符重载是一项功能强大的特性,但与大多数强大功能一样,它也是危险的。在编程中,伴随强大的能力而来的是巨大的责任。作为一名培训师,我经常看到人们在刚刚发现操作符重载时会过度使用。例如,一个练习涉及创建一个计算阶乘的函数: 4 | 5 | ``` kotlin 6 | fun Int.factorial(): Int = (1..this).product() 7 | 8 | fun Iterable.product(): Int = 9 | fold(1) { acc, i -> acc * i } 10 | ``` 11 | 12 | 由于这个函数被定义为`Int`的扩展函数,使用起来非常方便: 13 | 14 | ``` kotlin 15 | print(10 * 6.factorial()) // 7200 16 | ``` 17 | 18 | 数学家知道,阶乘有一个特殊的表示法,即在数字后面加一个感叹号: 19 | 20 | ``` kotlin 21 | 10 * 6! 22 | ``` 23 | 24 | 在Kotlin中并没有支持这样的操作符,但是正如我的一个研讨会参与者注意到的那样,我们可以使用`not`进行操作符重载: 25 | 26 | ``` kotlin 27 | operator fun Int.not() = factorial() 28 | 29 | print(10 * !6) // 7200 30 | ``` 31 | 32 | 虽然我们可以这样做,但我们是否应该这样做呢?最简单的答案是不应该。只需要读取函数的声明就可以注意到,这个函数的名称是`not`。正如这个名称所暗示的,它不应该被这样使用。它表示的是逻辑操作,而不是数值阶乘。这种用法会导致混乱和误导。在Kotlin中,所有操作符只是具有具体名称的函数的语法糖。下表展示了每个操作符在Kotlin中对应的具体名称。每个操作符都可以以函数的形式调用,而不是使用操作符的语法。下面的代码会是什么样子呢? 33 | 34 | ``` kotlin 35 | print(10 * 6.not()) // 7200 36 | ``` 37 | 38 | ![What each operator translates to in Kotlin.](../../assets/chapter2/chapter2-2.png) 39 | 40 | 在Kotlin中,每个操作符的含义始终保持不变。这是一个非常重要的设计决策。有些语言,比如Scala,允许您无限制地进行操作符重载。这种自由度被一些开发人员滥用的现象广为人知。即使函数和类的名称有意义,第一次阅读使用不熟悉的库的代码可能也很困难。现在想象一下,操作符被用于另一种意义,只有熟悉*范畴论*的开发人员才知道。理解起来将更加困难。您需要分别理解每个操作符,在特定上下文中记住它的含义,然后将所有这些都记住以连接各个部分来理解整个语句。在Kotlin中,我们没有这样的问题,因为每个操作符都有一个具体的含义。例如,当您看到以下表达式时: 41 | 42 | ``` kotlin 43 | x + y == z 44 | ``` 45 | 46 | 您知道这等同于: 47 | 48 | ``` kotlin 49 | x.plus(y).equal(z) 50 | ``` 51 | 52 | 或者如果`plus`声明了可为空的返回类型,可以是以下代码: 53 | 54 | ``` kotlin 55 | (x.plus(y))?.equal(z) ?: (z === null) 56 | ``` 57 | 58 | 这些都是具有具体名称的函数,我们期望所有函数都按照它们的名称所指示的方式工作。这严格限制了每个操作符可以用于什么目的。使用`not`来返回`factorial`是对这个约定的明显违反,不应该发生。 59 | 60 | ### 不清晰的情况 61 | 62 | 最大的问题是在某些情况下不清楚某种用法是否符合约定。例如,当我们将一个函数三倍化时,这意味着什么?对于某些人来说,意思是创建另一个重复该函数三次的函数: 63 | 64 | ``` kotlin 65 | operator fun Int.times(operation: () -> Unit): ()->Unit = 66 | { repeat(this) { operation() } } 67 | 68 | val tripledHello = 3 * { print("Hello") } 69 | 70 | tripledHello() // Prints: HelloHelloHello 71 | ``` 72 | 73 | 对于其他人来说,意思是我们要调用该函数三次: 74 | 75 | ``` kotlin 76 | operator fun Int.times(operation: ()->Unit) { 77 | repeat(this) { operation() } 78 | } 79 | 80 | 3 * { print("Hello") } // Prints: HelloHelloHello 81 | ``` 82 | 83 | 当意义不清楚时,最好使用描述性的扩展函数。如果我们希望保持类似操作符的语法,可以使用`infix`修饰符或顶层函数: 84 | 85 | ``` kotlin 86 | infix fun Int.timesRepeated(operation: ()->Unit) = { 87 | repeat(this) { operation() } 88 | } 89 | 90 | val tripledHello = 3 timesRepeated { print("Hello") } 91 | tripledHello() // Prints: HelloHelloHello 92 | ``` 93 | 94 | 有时候最好使用一个顶层函数。重复调用函数三次的功能已经在标准库中实现: 95 | 96 | ``` kotlin 97 | repeat(3) { print("Hello") } // Prints: HelloHelloHello 98 | ``` 99 | 100 | ### 什么时候可以打破这个规则? 101 | 102 | 有一个非常重要的情况可以打破这个规则:当我们设计领域特定语言(DSL)时。想象一个经典的HTML DSL示例: 103 | 104 | ``` kotlin 105 | body { 106 | div { 107 | +"Some text" 108 | } 109 | } 110 | ``` 111 | 112 | 您可以看到,为了将文本添加到元素中,我们使用了`String.unaryPlus`。这是可以接受的,因为它显然是领域特定语言(DSL)的一部分。在这个特定的上下文中,读者并不会对使用不同规则感到惊讶。 113 | 114 | ### 总结 115 | 116 | 要谨慎使用操作符重载。函数名称应与其行为一致。避免操作符含义不清楚的情况。通过使用具有描述性名称的常规函数来澄清。如果希望具有更类似操作符的语法,则可以使用`infix`修饰符或顶层函数。 -------------------------------------------------------------------------------- /docs/Part 1 Good code/Chapter 2 Readability/Item 13 Avoid returning or operating on Unit?.md: -------------------------------------------------------------------------------- 1 | ## 第13条:避免返回或操作 `Unit?` 2 | 3 | 在招聘过程中,我的一位亲密朋友被问到:“为什么有人希望从函数返回 `Unit?`?”嗯,`Unit?` 只有两个可能的值:`Unit` 或 `null`。就像 `Boolean` 可以是 `true` 或 `false` 一样。因此,这些类型是同构的,因此它们可以互换使用。为什么我们要使用 `Unit?` 而不是 `Boolean` 来表示某个东西呢?我没有其他答案,除了可以使用 Elvis 操作符或安全调用。所以,我们可以这样做: 4 | 5 | ```kotlin 6 | fun keyIsCorrect(key: String): Boolean = //... 7 | 8 | if(!keyIsCorrect(key)) return 9 | ``` 10 | 11 | 我们也可以这样做: 12 | 13 | ```kotlin 14 | fun verifyKey(key: String): Unit? = //... 15 | 16 | verifyKey(key) ?: return 17 | ``` 18 | 19 | 这似乎是预期的答案。但是,我朋友面试中缺少一个更重要的问题:“我们应该这样做吗?”虽然这种技巧在编写代码时看起来不错,但在阅读代码时可能并非如此。使用 `Unit?` 来表示逻辑值是具有误导性的,可能会导致难以检测的错误。我们已经讨论过这种表达式可能会带来意外结果的情况: 20 | 21 | ```kotlin 22 | getData()?.let{ view.showData(it) } ?: view.showError() 23 | ``` 24 | 25 | 当 `showData` 返回 `null` 而 `getData` 返回非 `null` 时,`showData` 和 `showError` 都将被调用。使用标准的 if-else 结构可以更少出错且更易读: 26 | 27 | ```kotlin 28 | if (person != null && person.isAdult) { 29 | view.showPerson(person) 30 | } else { 31 | view.showError() 32 | } 33 | ``` 34 | 35 | 比较以下两种写法: 36 | 37 | ```kotlin 38 | if(!keyIsCorrect(key)) return 39 | 40 | verifyKey(key) ?: return 41 | ``` 42 | 43 | 我从未找到过任何情况下 `Unit?` 是最可读选项的案例。它具有误导性和困惑性。几乎总是应该用 `Boolean` 来替代它。这就是为什么通常规定应避免返回或操作 `Unit?` 的一般原则。 -------------------------------------------------------------------------------- /docs/Part 1 Good code/Chapter 2 Readability/Item 14 Specify the variable type when it is not clear.md: -------------------------------------------------------------------------------- 1 | ## 第14条:在类型不明确的情况下,请显式声明变量的类型 2 | 3 | Kotlin拥有一个很好的类型推断系统,使我们能够在类型对开发者来说是显而易见的情况下省略类型声明: 4 | 5 | ```kotlin 6 | val num = 10 7 | val name = "Marcin" 8 | val ids = listOf(12, 112, 554, 997) 9 | ``` 10 | 11 | 这不仅提高了开发时间,而且在类型从上下文中明确时,额外的指定会显得冗余和混乱。然而,在类型不清楚的情况下,不应滥用这种方式: 12 | 13 | ```kotlin 14 | val data = getSomeData() 15 | ``` 16 | 17 | 我们为了可读性而设计我们的代码,不应隐藏对读者可能很重要的重要信息。不能以返回类型可以省略为理由,因为读者始终可以跳转到函数的规范中查看。类型也可能在那里被推断,用户可能会陷入越来越深的细节。此外,用户可能在GitHub或其他不支持跳转到实现的环境中阅读此代码。即使他们可以,我们都有非常有限的工作记忆,像这样浪费它并不是一个好主意。类型是重要的信息,如果不清楚,应该明确指定。 18 | 19 | ```kotlin 20 | val data: UserData = getSomeData() 21 | ``` 22 | 23 | 提高可读性并不是类型规定的唯一场景。它还涉及到安全性,就像在 *Chapter: Safety* 的 *Item 3: Eliminate platform types as soon as possible* 和 *Item 4: Do not expose inferred types* 中所示。**类型对开发者和编译器都是重要的信息。每当它是重要的信息时,请毫不犹豫地指定类型。它的代价很小,但可以帮助很多。** -------------------------------------------------------------------------------- /docs/Part 1 Good code/Chapter 2 Readability/Item 16 Properties should represent state not behavior.md: -------------------------------------------------------------------------------- 1 | ## 第16条:属性应代表状态,而不是行为 2 | 3 | Kotlin的属性看起来类似于Java的字段,但它们实际上代表了一个不同的概念。 4 | 5 | ``` kotlin 6 | // Kotlin属性 7 | var name: String? = null 8 | 9 | // Java字段 10 | String name = null; 11 | ``` 12 | 13 | 尽管它们可以以相同的方式使用,来存储数据,我们需要记住,属性具有更多的功能。首先,它们总是可以有自定义的设置器和获取器: 14 | 15 | ``` kotlin 16 | var name: String? = null 17 | get() = field?.toUpperCase() 18 | set(value) { 19 | if(!value.isNullOrBlank()) { 20 | field = value 21 | } 22 | } 23 | ``` 24 | 25 | 你可以看到我们在这里使用了`field`标识符。这是对让我们在此属性中保存数据的后备字段的引用。这样的后备字段是默认生成的,因为设置器和获取器的默认实现使用它们。我们也可以实现不使用它们的自定义访问器,在这种情况下,属性将根本没有`field`。例如,Kotlin属性可以只使用获取器定义为只读属性`val`: 26 | 27 | ``` kotlin 28 | val fullName: String 29 | get() = "$name $surname" 30 | ``` 31 | 32 | 对于可读写的属性`var`,我们可以通过定义获取器和设置器来创建一个属性。这样的属性被称为*派生属性*,并且并不少见。它们是Kotlin中所有属性默认被封装的主要原因。只需想象一下,你需要在你的对象中保存一个日期,你使用了Java stdlib的`Date`。然后在某个时候,由于某种原因,对象无法再存储这种类型的属性。可能是因为序列化问题,或者可能是因为你将此对象提升到了一个公共模块。问题在于,这个属性已经在你的项目中被引用。有了Kotlin,这就不再是问题,因为你可以将你的数据移动到一个单独的属性`millis`,并修改`date`属性,使其不再保存数据,而是包装/解包那个其他属性。 33 | 34 | ``` kotlin 35 | var date: Date 36 | get() = Date(millis) 37 | set(value) { 38 | millis = value.time 39 | } 40 | ``` 41 | 42 | 属性不需要字段。从概念上讲,它们代表访问器(`val`的 getter,`var`的 getter 和 setter)。这就是我们可以在接口中定义它们的原因: 43 | 44 | ``` kotlin 45 | interface Person { 46 | val name: String 47 | } 48 | ``` 49 | 50 | 这意味着这个接口承诺有一个 getter。我们也可以覆盖属性: 51 | 52 | ``` kotlin 53 | open class Supercomputer { 54 | open val theAnswer: Long = 42 55 | } 56 | 57 | class AppleComputer : Supercomputer() { 58 | override val theAnswer: Long = 1_800_275_2273 59 | } 60 | ``` 61 | 62 | 出于同样的原因,我们可以委托属性: 63 | 64 | ``` kotlin 65 | val db: Database by lazy { connectToDb() } 66 | ``` 67 | 68 | 属性委托在第21条:使用属性委托提取常见的属性模式中详细描述。因为属性本质上是函数,我们也可以创建扩展属性: 69 | 70 | ``` kotlin 71 | val Context.preferences: SharedPreferences 72 | get() = PreferenceManager 73 | .getDefaultSharedPreferences(this) 74 | 75 | val Context.inflater: LayoutInflater 76 | get() = getSystemService( 77 | Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater 78 | 79 | val Context.notificationManager: NotificationManager 80 | get() = getSystemService(Context.NOTIFICATION_SERVICE) 81 | as NotificationManager 82 | ``` 83 | 84 | 如你所见,**属性代表访问器,而不是字段**。这样,它们可以代替一些函数,但我们应该小心我们用它们来做什么。属性不应该用来表示像下面的例子中的算法行为: 85 | 86 | ``` kotlin 87 | // DON’T DO THIS! 88 | val Tree.sum: Int 89 | get() = when (this) { 90 | is Leaf -> value 91 | is Node -> left.sum + right.sum 92 | } 93 | ``` 94 | 95 | "这里的 `sum` 属性遍历所有元素,因此它代表了算法行为。因此,这个属性具有误导性:对于大型集合,寻找答案可能在计算上很重,这完全出乎预料。这不应该是一个属性,而应该是一个函数: 96 | 97 | ``` kotlin 98 | fun Tree.sum(): Int = when (this) { 99 | is Leaf -> value 100 | is Node -> left.sum() + right.sum() 101 | } 102 | ``` 103 | 104 | 一般规则是,**我们只应该用它们来表示或设置状态,不应该涉及其他逻辑**。一个有用的启发式规则来决定是否应该是一个属性是:如果我将这个属性定义为一个函数,我会在它前面加上get/set吗?如果不是,那么它可能不应该是一个属性。更具体地说,以下是我们不应该使用属性,而应该使用函数的最典型情况: 105 | 106 | - **操作在计算上昂贵或计算复杂度高于O(1)** - 用户不期望使用属性可能会很昂贵。如果是这样,使用函数更好,因为它传达了可能会这样,用户可能会节俭地使用它,或者开发者可能会考虑缓存它。 107 | 108 | - **它涉及业务逻辑(应用程序如何行动)** - 当我们阅读代码时,我们不期望一个属性可能会做任何超过简单动作的事情,比如记录,通知监听器,或者更新一个绑定的元素。 109 | 110 | - **它不是确定性的** - 连续两次调用成员会产生不同的结果。 111 | 112 | - **它是一个转换,比如** `Int.toDouble()` - 按照惯例,转换是一个方法或一个扩展函数。使用属性会像引用某个内部部分,而不是包装整个对象。 113 | 114 | - **获取器不应改变属性状态** - 我们期望我们可以自由地使用获取器,而不用担心属性状态的修改。 115 | 116 | 117 | 118 | 例如,计算元素的总和需要遍历所有元素(这是行为,而不是状态),并且具有线性复杂性。因此,它不应该是一个属性,并且在标准库中被定义为一个函数: 119 | 120 | ``` kotlin 121 | val s = (1..100).sum() 122 | ``` 123 | 124 | 另一方面,为了获取和设置状态,我们在Kotlin中使用属性,除非有充分的理由,否则我们不应该涉及到函数。我们使用属性来表示和设置状态,如果你以后需要修改它们,使用自定义的获取器和设置器: 125 | 126 | ``` kotlin 127 | // DON’T DO THIS! 128 | class UserIncorrect { 129 | private var name: String = "" 130 | 131 | fun getName() = name 132 | 133 | fun setName(name: String) { 134 | this.name = name 135 | } 136 | } 137 | 138 | class UserCorrect { 139 | var name: String = "" 140 | } 141 | ``` 142 | 143 | 一个简单的经验法则是,**属性描述和设置状态,而函数描述行为**。 144 | 145 | -------------------------------------------------------------------------------- /docs/Part 1 Good code/Chapter 2 Readability/Item 17 Consider naming arguments.md: -------------------------------------------------------------------------------- 1 | ## 第17条:考虑命名参数 2 | 3 | 当你阅读代码时,参数的含义并不总是清晰的。看看下面的例子: 4 | 5 | ``` kotlin 6 | val text = (1..10).joinToString("|") 7 | ``` 8 | 9 | `"|"`是什么?如果你对`joinToString`很熟悉,你就知道它是`separator`。虽然它也可能是`prefix`。这一点完全不清楚。我们可以通过明确那些值并不清楚其含义的参数来使其更易读。最好的方法就是使用命名参数: 10 | 11 | ``` kotlin 12 | val text = (1..10).joinToString(separator = "|") 13 | ``` 14 | 15 | 我们也可以通过命名变量达到类似的效果: 16 | 17 | ``` kotlin 18 | val separator = "|" 19 | val text = (1..10).joinToString(separator) 20 | ``` 21 | 22 | 尽管命名参数更可靠。变量名指定了开发者的意图,但并不一定是正确的。如果开发者犯了错误,把变量放在了错误的位置呢?如果参数的顺序改变了呢?命名参数可以保护我们免受这种情况的影响,而命名值则不能。这就是为什么当我们有命名值时,仍然有理由使用命名参数: 23 | 24 | ``` kotlin 25 | val separator = "|" 26 | val text = (1..10).joinToString(separator = separator) 27 | ``` 28 | 29 | ### 我们应该何时使用命名参数? 30 | 31 | 显然,命名参数更长,但它们有两个重要的优点: 32 | 33 | - 名称指示预期的值。 34 | - 它们更安全,因为它们独立于顺序。 35 | 36 | 参数名称不仅对使用此函数的开发者重要,对阅读它如何被使用的人也重要。看看这个调用: 37 | 38 | ``` kotlin 39 | sleep(100) 40 | ``` 41 | 42 | 它会睡多久?100毫秒?可能是100秒?我们可以使用命名参数来澄清它: 43 | 44 | ``` kotlin 45 | sleep(timeMillis = 100) 46 | ``` 47 | 48 | 在这种情况下,这并不是唯一的澄清选项。在像Kotlin这样的静态类型语言中,当我们传递参数时,保护我们的第一个机制是参数类型。我们可以在这里使用它来表达关于时间单位的信息: 49 | 50 | ``` kotlin 51 | sleep(Millis(100)) 52 | ``` 53 | 54 | 或者,我们可以使用扩展属性来创建类似DSL的语法: 55 | 56 | ``` kotlin 57 | sleep(100.ms) 58 | ``` 59 | 60 | 类型是传递此类信息的好方法。如果你关心效率,可以使用*第46条:对具有功能类型参数的函数使用内联修饰符*中描述的内联类。它们帮助我们保证参数的安全性,但并不能解决所有问题。一些参数可能仍然不清楚。一些参数可能仍然被放在错误的位置。这就是为什么我仍然建议考虑使用命名参数,特别是对于参数: 61 | 62 | - 带有默认参数的, 63 | - 与其他参数类型相同的, 64 | - 功能类型的,如果它们不是最后一个参数。 65 | 66 | ### 带有默认参数的参数 67 | 68 | 当一个属性有一个默认参数时,我们几乎总是应该通过名称使用它。这种可选参数比那些必需的参数更常更改。我们不想错过这样的变化。函数名称通常指示其非可选参数是什么,但不是其可选参数是什么。这就是为什么命名可选参数通常更安全、更清洁的原因。 69 | 70 | ### 许多类型相同的参数 71 | 72 | 正如我们所说,当参数有不同的类型时,我们通常可以避免将参数放在错误的位置。但是,当一些参数有相同的类型时,就没有这样的自由了。 73 | 74 | ``` kotlin 75 | fun sendEmail(to: String, message: String) { /*...*/ } 76 | ``` 77 | 78 | 对于这样的函数,最好使用名称来澄清参数: 79 | 80 | ``` kotlin 81 | sendEmail( 82 | to = "contact@kt.academy", 83 | message = "Hello, ..." 84 | ) 85 | ``` 86 | 87 | ### 功能类型的参数 88 | 89 | 最后,我们应该特别对待具有函数类型的参数。在Kotlin中,这样的参数有一个特殊的位置:最后一个位置。有时,函数名描述了一个函数类型的参数。例如,当我们看到`repeat`时,我们期望在其后的lambda是应该被重复的代码块。当你看到`thread`时,直观地认为在其后的块是这个新线程的主体。这样的名称只描述了在最后位置使用的函数。 90 | 91 | ``` kotlin 92 | thread { 93 | // ... 94 | } 95 | ``` 96 | 97 | 所有其他具有函数类型的参数都应该被命名,因为很容易误解它们。例如,看看这个简单的视图DSL: 98 | 99 | ``` kotlin 100 | val view = linearLayout { 101 | text("Click below") 102 | button({ /* 1 */ }, { /* 2 */ }) 103 | } 104 | ``` 105 | 106 | 哪个函数是这个构建器的一部分,哪个是点击监听器?我们应该通过命名监听器并将构建器移出参数来澄清这一点: 107 | 108 | ``` kotlin 109 | val view = linearLayout { 110 | text("Click below") 111 | button(onClick = { /* 1 */ }) { 112 | /* 2 */ 113 | } 114 | } 115 | ``` 116 | 117 | 函数类型的多个可选参数可能会特别令人困惑: 118 | 119 | ``` kotlin 120 | fun call(before: ()->Unit = {}, after: ()->Unit = {}){ 121 | before() 122 | print("Middle") 123 | after() 124 | } 125 | 126 | call({ print("CALL") }) // CALLMiddle 127 | call { print("CALL") } // MiddleCALL 128 | ``` 129 | 130 | 为了防止这种情况,当没有一个具有特殊含义的函数类型的参数时,给它们全部命名: 131 | 132 | ``` kotlin 133 | call(before = { print("CALL") }) // CALLMiddle 134 | call(after = { print("CALL") }) // MiddleCALL 135 | ``` 136 | 137 | 对于反应式库来说,这一点尤其重要。例如,在RxJava中,当我们订阅一个`Observable`时,我们可以设置应该被调用的函数: 138 | 139 | - 在每个接收到的项目上 140 | - 在出现错误的情况下 141 | - 在observable完成后 142 | 143 | 在Java中,我经常看到人们使用lambda表达式来设置它们,并在注释中指定每个lambda表达式是哪个方法。 144 | 145 | ``` kotlin 146 | // Java 147 | observable.getUsers() 148 | .subscribe((List users) -> { // onNext 149 | // ... 150 | }, (Throwable throwable) -> { // onError 151 | // ... 152 | }, () -> { // onCompleted 153 | // ... 154 | }); 155 | ``` 156 | 157 | 在 Kotlin 中,我们可以进一步使用命名参数: 158 | 159 | ``` kotlin 160 | observable.getUsers() 161 | .subscribeBy( 162 | onNext = { users: List -> 163 | // ... 164 | }, 165 | onError = { throwable: Throwable -> 166 | // ... 167 | }, 168 | onCompleted = { 169 | // ... 170 | }) 171 | ``` 172 | 173 | 注意,我将函数名从 `subscribe` 更改为 `subscribeBy`。这是因为 `RxJava` 是用 Java 编写的,**我们在调用 Java 函数时不能使用命名参数**。这是因为 Java 不保留有关函数名称的信息。为了能够使用命名参数,我们通常需要为这些函数制作我们的 Kotlin 包装器(作为这些函数的替代方案的扩展函数)。 174 | 175 | ### 总结 176 | 177 | 命名参数不仅在我们需要跳过一些默认值时有用。它们对于阅读我们代码的开发人员来说是重要的信息,它们可以提高我们代码的安全性。当我们有更多相同类型的参数(或具有功能类型的参数)和可选参数时,我们应该考虑它们。当我们有多个具有功能类型的参数时,它们几乎总是应该被命名。例外情况是最后一个函数参数,当它具有特殊含义,如在 DSL 中。 -------------------------------------------------------------------------------- /docs/Part 1 Good code/Chapter 2 Readability/Item 18 Respect coding conventions.md: -------------------------------------------------------------------------------- 1 | ## 第18条:尊重编码规范 2 | 3 | Kotlin有着在文档中详细描述的成熟编码规范,这部分被恰当地称为“编码规范”。**这些规范并不适用于所有项目,但对我们这个社区来说,最好的方式是在所有项目中都遵守这些规范。**多亏了这些规范: 4 | 5 | - 在项目之间切换更容易 6 | - 代码对外部开发者来说更易读 7 | - 更容易猜测代码如何运行 8 | - 更容易将代码与公共仓库合并,或者将代码的某些部分从一个项目移动到另一个项目 9 | 10 | 程序员应该熟悉文档中描述的这些规范。他们也应该在规范变化时尊重这些规范 - 这可能会在一定程度上随着时间的推移发生。由于这两者都很难做到,所以有两个工具可以帮助: 11 | 12 | - IntelliJ格式化器可以设置为自动按照官方编码规范样式进行格式化。为此,转到设置 | 编辑器 | 代码样式 | Kotlin,点击右上角的“Set from…”链接,并从菜单中选择“预定义样式 / Kotlin样式指南”。 13 | - ktlint - 流行的linter,可以分析你的代码并通知你所有的编码规范违规情况。 14 | 15 | 看看Kotlin项目,我发现大多数项目都与大多数规范直观地一致。这可能是因为Kotlin主要遵循Java编码规范,而大多数现在的Kotlin开发者都是Java开发者。我经常看到被违反的一条规则是类和函数应该如何格式化。根据规范,带有短主构造函数的类可以在一行中定义: 16 | 17 | ``` 18 | class FullName(val name: String, val surname: String) 19 | ``` 20 | 21 | 然而,带有许多参数的类应该格式化,使得每个参数都在另一行,且第一行没有参数: 22 | ``` kotlin 23 | class Person( 24 | val id: Int = 0, 25 | val name: String = "", 26 | val surname: String = "" 27 | ) : Human(id, name) { 28 | // 主体 29 | } 30 | ``` 31 | 32 | 同样,这是我们如何格式化一个长函数的方式: 33 | 34 | ``` kotlin 35 | public fun Iterable.joinToString( 36 | separator: CharSequence = ", ", 37 | prefix: CharSequence = "", 38 | postfix: CharSequence = "", 39 | limit: Int = -1, 40 | truncated: CharSequence = "...", 41 | transform: ((T) -> CharSequence)? = null 42 | ): String { 43 | // ... 44 | } 45 | ``` 46 | 47 | 注意,这两种方式与将第一个参数留在同一行,然后将所有其他参数缩进到它的约定非常不同。 48 | 49 | ``` kotlin 50 | // 不要这样做 51 | class Person(val id: Int = 0, 52 | val name: String = "", 53 | val surname: String = "") : Human(id, name){ 54 | // 主体 55 | } 56 | ``` 57 | 58 | 这可能会有两个问题: 59 | 60 | - 每个类的参数都以类名不同的缩进开始。此外,当我们更改类名时,我们需要调整所有主构造函数参数的缩进。 61 | - 这样定义的类仍然过于宽泛。这种方式定义的类的宽度是类名、`class`关键字和最长的主构造函数参数,或者最后一个参数加上超类和接口。 62 | 63 | 一些团队可能决定使用稍微不同的约定。这没问题,但那么这些约定应该在整个项目中得到尊重。**每个项目看起来都应该像是由一个人编写的,而不是一群人在互相斗争。** 64 | 65 | 编码约定通常不被开发者足够尊重,但它们很重要,而且在最佳实践书籍中关于可读性的章节中,不能没有至少一个简短的部分专门讨论它们。阅读它们,使用静态检查器帮助你与它们保持一致,将它们应用在你的项目中。通过尊重编码约定,我们使Kotlin项目对我们所有人都更好。 -------------------------------------------------------------------------------- /docs/Part 2 Code design/Chapter 3 Reusability/Introduction.md: -------------------------------------------------------------------------------- 1 | # Chapter 3: Reusability 2 | 3 | Have you ever wondered how the `System.out.print` function works (`print` function in Kotlin/JVM)? It’s one of the most basic functions used again and again and yet, would you be able to implement it yourself if it would vanish one day? Truth is that this is not an easy task. Especially if the rest of java.io would vanish as well. You would need to implement the communication with the operating system in C using JNI, separately for each operating system you support[1](chap65.xhtml#fn-footnote_30_note). Believe me, implementing it once is terrifying. Implementing it again and again in every project would be a horror. 4 | 5 | The same applies to many other functions as well. Making Android views is so easy because Android has a complex API that supports it. A backend developer doesn’t need to know too much about the HTTP(S) protocol even though they work with it every day. You don’t need to know any sorting algorithms to call `Iterable.sorted`. Thankfully, we don’t need to have and use all this knowledge every day. Someone implemented it once, and now we can use it whenever we need. This demonstrates a key feature of Programming languages: *reusability*. 6 | 7 | Sounds enthusiastic, but code reusability is as dangerous as it is powerful. A small change in the `print` function could break countless programs. If we extract a common part from A and B, we have an easier job in the future if we need to change them both, but it’s harder and more error-prone when we need to change only one. 8 | 9 | This chapter is dedicated to reusability. It touches on many subjects that developers do intuitively. We do so because we learned them through practice. Mainly through observations of how something we did in the past has an impact on us now. We extracted something, and now it causes problems. We haven’t extracted something, and now we have a problem when we need to make some changes. Sometimes we deal with code written by different developers years ago, and we see how their decisions impact us now. Maybe we just looked at another language or project and we thought “Wow, this is short and readable because they did X”. This is the way we typically learn, and this is one of the best ways to learn. 10 | 11 | It has one problem though: it requires years of practice. To speed it up and to help you systematize this knowledge, this chapter will give you some generic rules to help you make your code better in the long term. It is a bit more theoretical than the rest of the book. If you’re looking for concrete rules (as presented in the previous chapters), feel free to skip it. -------------------------------------------------------------------------------- /docs/Part 2 Code design/Chapter 3 Reusability/Item 22 Use generics when implementing common algorithms.md: -------------------------------------------------------------------------------- 1 | ## Item 22: Use generics when implementing common algorithms 2 | 3 | Similarly, as we can pass a value to a function as an argument, we can pass a type as a type argument. Functions that accept type arguments (so having type parameters) are called generic functions. One known example is the `filter` function from stdlib that has type parameter `T`: 4 | 5 | ``` kotlin 6 | inline fun Iterable.filter( 7 | predicate: (T) -> Boolean 8 | ): List { 9 | val destination = ArrayList() 10 | for (element in this) { 11 | if (predicate(element)) { 12 | destination.add(element) 13 | } 14 | } 15 | return destination 16 | } 17 | ``` 18 | 19 | Type parameters are useful to the compiler since they allow it to check and correctly infer types a bit further, what makes our programs safer and programming more pleasurable for developers. For instance, when we use `filter`, inside the lambda expression, the compiler knows that an argument is of the same type as the type of elements in the collection, so it protects us from using something illegal and the IDE can give us useful suggestions. 20 | 21 | ![](../../assets/chapter1/chapter1-3.png) 22 | 23 | Generics were primarily introduced to classes and interfaces to allow the creation of collections with only concrete types, like `List` or `Set`. Those types are lost during compilation but when we are developing, the compiler forces us to pass only elements of the correct type. For instance `Int` when we add to `MutableList`. Also, thanks to them, the compiler knows that the returned type is `User` when we get an element from `Set`. This way type parameters help us a lot in statically-typed languages. Kotlin has powerful support for generics that is not well understood and from my experience even experienced Kotlin developers have gaps in their knowledge especially about variance modifiers. So let’s discuss the most important aspects of Kotlin generics in this and in *Item 24: Consider variance for generic types*. 24 | 25 | ### Generic constraints 26 | 27 | One important feature of type parameters is that they can be constrained to be a subtype of a concrete type. We set a constraint by placing supertype after a colon. This type can include previous type parameters: 28 | 29 | ``` kotlin 30 | fun > Iterable.sorted(): List { 31 | /*...*/ 32 | } 33 | 34 | fun > 35 | Iterable.toCollection(destination: C): C { 36 | /*...*/ 37 | } 38 | 39 | class ListAdapter(/*...*/) { /*...*/ } 40 | ``` 41 | 42 | One important result of having a constraint is that instances of this type can use all the methods this type offers. This way when `T` is constrained as a subtype of `Iterable`, we know that we can iterate over an instance of type `T`, and that elements returned by the iterator will be of type `Int`. When we constraint to `Comparable`, we know that this type can be compared with itself. Another popular choice for a constraint is `Any` which means that a type can be any non-nullable type: 43 | 44 | ``` kotlin 45 | inline fun Iterable.mapNotNull( 46 | transform: (T) -> R? 47 | ): List { 48 | return mapNotNullTo(ArrayList(), transform) 49 | } 50 | ``` 51 | 52 | In rare cases in which we might need to set more than one upper bound, we can use `where` to set more constraints: 53 | 54 | ``` kotlin 55 | fun pet(animal: T) where T: GoodTempered { 56 | /*...*/ 57 | } 58 | 59 | // OR 60 | 61 | fun pet(animal: T) where T: Animal, T: GoodTempered { 62 | /*...*/ 63 | } 64 | ``` 65 | 66 | ### Summary 67 | 68 | Type parameters are an important part of Kotlin typing system. We use them to have type-safe generic algorithms or generic objects. Type parameters can be constrained to be a subtype of a concrete type. When they are, we can safely use methods offered by this type. -------------------------------------------------------------------------------- /docs/Part 2 Code design/Chapter 3 Reusability/Item 23 Avoid shadowing type parameters.md: -------------------------------------------------------------------------------- 1 | ## Item 23: Avoid shadowing type parameters 2 | 3 | It is possible to define property and parameters with the same name due to shadowing. Local parameter shadows outer scope property. There is no warning because such a situation is not uncommon and is rather visible for developers: 4 | 5 | ``` kotlin 6 | class Forest(val name: String) { 7 | 8 | fun addTree(name: String) { 9 | // ... 10 | } 11 | } 12 | ``` 13 | 14 | On the other hand, the same can happen when we shadow class type parameter with a function type parameter. Such a situation is less visible and can lead to serious problems. This mistake is often done by developers not understanding well how generics work. 15 | 16 | ``` kotlin 17 | interface Tree 18 | class Birch: Tree 19 | class Spruce: Tree 20 | 21 | class Forest { 22 | 23 | fun addTree(tree: T) { 24 | // ... 25 | } 26 | } 27 | ``` 28 | 29 | The problem is that now `Forest` and `addTree` type parameters are independent of each other: 30 | 31 | ``` kotlin 32 | val forest = Forest() 33 | forest.addTree(Birch()) 34 | forest.addTree(Spruce()) 35 | ``` 36 | 37 | Such situation is rarely desired and might be confusing. One solution is that `addTree` should use the class type parameter `T`: 38 | 39 | ``` kotlin 40 | class Forest { 41 | 42 | fun addTree(tree: T) { 43 | // ... 44 | } 45 | } 46 | 47 | // Usage 48 | val forest = Forest() 49 | forest.addTree(Birch()) 50 | forest.addTree(Spruce()) // ERROR, type mismatch 51 | ``` 52 | 53 | If we need to introduce a new type parameter, it is better to name it differently. Note that it can be constrained to be a subtype of the other type parameter: 54 | 55 | ``` kotlin 56 | class Forest { 57 | 58 | fun addTree(tree: ST) { 59 | // ... 60 | } 61 | } 62 | ``` 63 | 64 | ### Summary 65 | 66 | Avoid shadowing type parameters, and be careful when you see that type parameter is shadowed. Unlike for other kinds of parameters, it is not intuitive and might be highly confusing. -------------------------------------------------------------------------------- /docs/Part 2 Code design/Chapter 4 Abstraction design/Item 29 Consider wrapping external API.md: -------------------------------------------------------------------------------- 1 | ## Item 29: Consider wrapping external API 2 | 3 | It is risky to heavily use an API that might be unstable. Both when creators clarify that it is unstable, and when we do not trust those creators to keep it stable. Remembering that we need to adjust every use in case of inevitable API change, we should consider limiting uses and separate them from our logic as much as possible. This is why we often wrap potentially unstable external library APIs in our own project. This gives us a lot of freedom and stability: 4 | 5 | - We are not afraid of API changes because we would only need to change a single usage inside the wrapper. 6 | - We can adjust the API to our project style and logic. 7 | - We can replace it with a different library in case of some problems with this one. 8 | - We can change the behavior of these objects if we need to (of course, do it responsibly). 9 | 10 | There are also counterarguments to this approach: 11 | 12 | - We need to define all those wrappers. 13 | - Our internal API is internal, and developers need to learn it just for this project. 14 | - There are no courses teaching how our internal API works. We should also not expect answers on Stack Overflow. 15 | 16 | Knowing both sides, you need to decide which APIs should be wrapped. A good heuristics that tells us how stable a library is are version number and the number of users. Generally, the more users the library has, the more stable it is. Creators are more careful with changes when they know that their small change might require corrections in many projects. The riskiest libraries are new ones with small popularity. Use them wisely and consider wrapping them into your own classes and functions to control them internally. -------------------------------------------------------------------------------- /docs/Part 2 Code design/Chapter 4 Abstraction design/Item 32 Respect abstraction contracts.md: -------------------------------------------------------------------------------- 1 | ## Item 32: Respect abstraction contracts 2 | 3 | Both contract and visibility are kind of an agreement between developers. This agreement nearly always can be violated by a user. Technically, everything in a single project can be hacked. For instance, it is possible to use reflection to open and use anything we want: 4 | 5 | ``` kotlin 6 | class Employee { 7 | private val id: Int = 2 8 | override fun toString() = "User(id=$id)" 9 | 10 | private fun privateFunction() { 11 | println("Private function called") 12 | } 13 | } 14 | 15 | fun callPrivateFunction(employee: Employee) { 16 | employee::class.declaredMemberFunctions 17 | .first { it.name == "privateFunction" } 18 | .apply { isAccessible = true } 19 | .call(employee) 20 | } 21 | 22 | fun changeEmployeeId(employee: Employee, newId: Int) { 23 | employee::class.java.getDeclaredField("id") 24 | .apply { isAccessible = true } 25 | .set(employee, newId) 26 | } 27 | 28 | fun main() { 29 | val employee = Employee() 30 | callPrivateFunction(employee) 31 | // Prints: Private function called 32 | 33 | changeEmployeeId(employee, 1) 34 | print(employee) // Prints: User(id=1) 35 | } 36 | ``` 37 | 38 | Just because you can do something, doesn’t mean that it is fine to do it. Here we very strongly depend on the implementation details like the names of the private property and the private function. They are not part of a contract at all, and so they might change at any moment. This is like a ticking bomb for our program. 39 | 40 | Remember that a contract is like a warranty. As long as you use your computer correctly, the warranty protects you. When you open your computer and start hacking it, you lose your warranty. The same principle applies here: when you break the contract, it is your problem when implementation changes and your code stops working. 41 | 42 | ### Contracts are inherited 43 | 44 | It is especially important to respect contracts when we inherit from classes, or when we extend interfaces from another library. Remember that your object should respect their contracts. For instance, every class extends `Any` that have `equals`and `hashCode` methods. They both have well-established contracts that we need to respect. If we don’t, our objects might not work correctly. For instance, when `hashCode` is not consistent with `equals`, our object might not behave correctly on `HashSet`. Below behavior is incorrect because a set should not allow duplicates: 45 | 46 | ``` kotlin 47 | class Id(val id: Int) { 48 | override fun equals(other: Any?) = 49 | other is Id && other.id == id 50 | } 51 | 52 | val mutableSet = mutableSetOf(Id(1)) 53 | mutableSet.add(Id(1)) 54 | mutableSet.add(Id(1)) 55 | print(mutableSet.size) // 3 56 | ``` 57 | 58 | In this case, it is that `hashCode` do not have implementation consistent with `equals`. We will discuss some important Kotlin contracts in *Chapter 6: Class design*. For now, remember to check the expectations on functions you override, and respect those. 59 | 60 | ### Summary 61 | 62 | If you want your programs to be stable, respect contracts. If you are forced to break them, document this fact well. Such information will be very helpful to whoever will maintain your code. Maybe that will be you, in a few years’ time. -------------------------------------------------------------------------------- /docs/Part 2 Code design/Chapter 5 Object creation/Introduction.md: -------------------------------------------------------------------------------- 1 | # Chapter 5: Object creation 2 | 3 | Although Kotlin can be written in a purely functional style, it can also be written in object oriented programming (OOP), much like Java. In OOP, we need to create every object we use, or at least define how it ought to be created, and different ways have different characteristics. It is important to know what options do we have. This is why this chapter shows different ways how we can define object creation, and explains their advantages and disadvantages. 4 | 5 | If you are familiar with the *Effective Java* book by Joshua Bloch, then you may notice some similarities between this chapter and that book. It is no coincidence. This chapter relates to the first chapter of Effective Java. Although Kotlin is very different from Java, and there are only morsels of knowledge that can be used. For instance, static methods are not allowed in Kotlin, but we have very good alternatives like top-level functions and companion object functions. They don’t work the same way as static functions, so it is important to understand them. Similarly, with other items, you can notice similarities, but the changes that Kotlin has introduced are important. To cheer you up: these changes are mostly to provide more possibilities or force better style. Kotlin is a powerful and really well-designed language, and this chapter should mainly open your eyes to these new possibilities. -------------------------------------------------------------------------------- /docs/Part 2 Code design/Chapter 6 Class design/Introduction.md: -------------------------------------------------------------------------------- 1 | # Chapter 6: Class design 2 | 3 | Classes are the most important abstraction in the Object-Oriented Programming (OOP) paradigm. Since OOP is the most popular paradigm in Kotlin, classes are very important for us as well. This chapter is about class design. Not about system design, since it would require much more space and there are already many great books on this topic such as Clean Architecture by Robert C. Martin or Design Patterns by Erich Gamma, John Vlissides, Ralph Johnson, and Richard Helm. Instead, we will mainly talk about contracts that Kotlin classes are expected to fulfill - how we use Kotlin structures and what is expected from us when we use them. When and how should we use inheritance? How do we expect data classes to be used? When should we use function types instead of interfaces with a single method? What are the contracts of `equals`, `hashCode` and `compareTo`? When should we use extensions instead of members? These are the kind of questions we will answer here. They are all important because breaking them might cause serious problems, and following them will help you make your code safer and cleaner. -------------------------------------------------------------------------------- /docs/Part 2 Code design/Chapter 6 Class design/Item 38 Use function types instead of interfaces to pass operations and actions.md: -------------------------------------------------------------------------------- 1 | ## Item 38: Use function types instead of interfaces to pass operations and actions 2 | 3 | Many languages do not have the concept of a function type. Instead, they use interfaces with a single method. Such interfaces are known as SAM’s (Single-Abstract Method). Here is an example SAM used to pass information about what should happen when a view is clicked: 4 | 5 | ``` kotlin 6 | interface OnClick { 7 | fun clicked(view: View) 8 | } 9 | ``` 10 | 11 | When a function expects a SAM, we must pass an instance of an object that implements this interface[1](chap65.xhtml#fn-footnote_610_note). 12 | 13 | ``` kotlin 14 | fun setOnClickListener(listener: OnClick) { 15 | //... 16 | } 17 | 18 | setOnClickListener(object : OnClick { 19 | override fun clicked(view: View) { 20 | // ... 21 | } 22 | }) 23 | ``` 24 | 25 | However, notice that declaring a parameter with a function type gives us much more freedom: 26 | 27 | ``` kotlin 28 | fun setOnClickListener(listener: (View) -> Unit) { 29 | //... 30 | } 31 | ``` 32 | 33 | Now, we can pass the parameter as: 34 | 35 | - A lambda expression or an anonymous function 36 | 37 | ``` kotlin 38 | setOnClickListener { /*...*/ } 39 | setOnClickListener(fun(view) { /*...*/ }) 40 | ``` 41 | 42 | - A function reference or bounded function reference 43 | 44 | ``` kotlin 45 | setOnClickListener(::println) 46 | setOnClickListener(this::showUsers) 47 | ``` 48 | 49 | - Objects that implement the declared function type 50 | 51 | ``` kotlin 52 | class ClickListener: (View)->Unit { 53 | override fun invoke(view: View) { 54 | // ... 55 | } 56 | } 57 | 58 | setOnClickListener(ClickListener()) 59 | ``` 60 | 61 | These options can cover a wider spectrum of use cases. On the other hand, one might argue that the advantage of a SAM is that it and its arguments are named. Notice that we can name function types using type aliases as well. 62 | 63 | ``` kotlin 64 | typealias OnClick = (View) -> Unit 65 | ``` 66 | 67 | Parameters can also be named. The advantage of naming them is that these names can then be suggested by default by an IDE. 68 | 69 | ``` kotlin 70 | fun setOnClickListener(listener: OnClick) { /*...*/ } 71 | typealias OnClick = (view: View)->Unit 72 | ``` 73 | 74 | ![](../../assets/chapter6/chapter6-3.png) 75 | 76 | Notice that when we use lambda expressions, we can also destructure arguments. Together, this makes function types generally a better option than SAMs. 77 | 78 | This argument is especially true when we set many observers. The classic Java way often is to collect them in a single listener interface: 79 | 80 | ``` kotlin 81 | class CalendarView { 82 | var listener: Listener? = null 83 | 84 | interface Listener { 85 | fun onDateClicked(date: Date) 86 | fun onPageChanged(date: Date) 87 | } 88 | } 89 | ``` 90 | 91 | I believe this is largely a result of laziness. From an API consumer’s point of view, it is better to set them as separate properties holding function types: 92 | 93 | ``` kotlin 94 | class CalendarView { 95 | var onDateClicked: ((date: Date) -> Unit)? = null 96 | var onPageChanged: ((date: Date) -> Unit)? = null 97 | } 98 | ``` 99 | 100 | This way, the implementations of `onDateClicked` and `onPageChanged` do not need to be tied together in an interface. Now, these functions may be changed independently. 101 | 102 | If you don’t have a good reason to define an interface, prefer using function types. They are well supported and are used frequently by Kotlin developers. 103 | 104 | ### When should we prefer a SAM? 105 | 106 | There is one case when we prefer a SAM: When we design a class to be used from another language than Kotlin. Interfaces are cleaner for Java clients. They cannot see type aliases nor name suggestions. Finally, Kotlin function types when used from some languages (especially Java) require functions to return `Unit` explicitly: 107 | 108 | ``` kotlin 109 | // Kotlin 110 | class CalendarView() { 111 | var onDateClicked: ((date: Date) -> Unit)? = null 112 | var onPageChanged: OnDateClicked? = null 113 | } 114 | 115 | interface OnDateClicked { 116 | fun onClick(date: Date) 117 | } 118 | 119 | // Java 120 | CalendarView c = new CalendarView(); 121 | c.setOnDateClicked(date -> Unit.INSTANCE); 122 | c.setOnPageChanged(date -> {}); 123 | ``` 124 | 125 | This is why it might be reasonable to use SAM instead of function types when we design API for use from Java. Though in other cases, prefer function types. -------------------------------------------------------------------------------- /docs/Part 2 Code design/Chapter 6 Class design/Item 44 Avoid member extensions.md: -------------------------------------------------------------------------------- 1 | ## Item 44: Avoid member extensions 2 | 3 | When we define an extension function to some class, it is not added to this class as a member. An extension function is just a different kind of function that we call on the first argument that is there, called a receiver. Under the hood, extension functions are compiled to normal functions, and the receiver is placed as the first parameter. For instance, the following function: 4 | 5 | ``` kotlin 6 | fun String.isPhoneNumber(): Boolean = 7 | length == 7 && all { it.isDigit() } 8 | ``` 9 | 10 | Under the hood is compiled to a function similar to this one: 11 | 12 | ``` kotlin 13 | fun isPhoneNumber(`$this`: String): Boolean = 14 | `$this`.length == 7 && `$this`.all { it.isDigit() } 15 | ``` 16 | 17 | One of the consequences of how they are implemented is that we can have member extensions or even define extensions in interfaces: 18 | 19 | ``` kotlin 20 | interface PhoneBook { 21 | fun String.isPhoneNumber(): Boolean 22 | } 23 | 24 | class Fizz: PhoneBook { 25 | override fun String.isPhoneNumber(): Boolean = 26 | length == 7 && all { it.isDigit() } 27 | } 28 | ``` 29 | 30 | Even though it is possible, there are good reasons to avoid defining member extensions (except for DSLs). **Especially, do not define extension as members just to restrict visibility**. 31 | 32 | ``` kotlin 33 | // Bad practice, do not do this 34 | class PhoneBookIncorrect { 35 | // ... 36 | 37 | fun String.isPhoneNumber() = 38 | length == 7 && all { it.isDigit() } 39 | } 40 | ``` 41 | 42 | One big reason is that it does not really restrict visibility. It only makes it more complicated to use the extension function since the user would need to provide both the extension and dispatch receivers: 43 | 44 | ``` kotlin 45 | PhoneBookIncorrect().apply { "1234567890".test() } 46 | ``` 47 | 48 | **You should restrict the extension visibility using a visibility modifier and not by making it a member.** 49 | 50 | ``` kotlin 51 | // This is how we limit extension functions visibility 52 | class PhoneBookCorrect { 53 | // ... 54 | } 55 | 56 | private fun String.isPhoneNumber() = 57 | length == 7 && all { it.isDigit() } 58 | ``` 59 | 60 | There are a few good reasons why we prefer to avoid member extensions: 61 | 62 | - Reference is not supported: 63 | 64 | ``` kotlin 65 | val ref = String::isPhoneNumber 66 | val str = "1234567890" 67 | val boundedRef = str::isPhoneNumber 68 | 69 | val refX = PhoneBookIncorrect::isPhoneNumber // ERROR 70 | val book = PhoneBookIncorrect() 71 | val boundedRefX = book::isPhoneNumber // ERROR 72 | ``` 73 | 74 | - Implicit access to both receivers might be confusing: 75 | 76 | ``` kotlin 77 | class A { 78 | val a = 10 79 | } 80 | class B { 81 | val a = 20 82 | val b = 30 83 | 84 | fun A.test() = a + b // Is it 40 or 50? 85 | } 86 | ``` 87 | 88 | - When we expect an extension to modify or reference a receiver, it is not clear if we modify the extension or dispatch receiver (the class in which the extension is defined): 89 | 90 | ``` kotlin 91 | class A { 92 | //... 93 | } 94 | class B { 95 | //... 96 | 97 | fun A.update() = ... // Does it update A or B? 98 | } 99 | ``` 100 | 101 | - For less experienced developers it might be counterintuitive or scary to see member extensions. 102 | 103 | To summarize, if there is a good reason to use a member extension, it is fine. Just be aware of the downsides and generally try to avoid it. To restrict visibility, use visibility modifiers. Just placing an extension in a class does not limit its use from outside. -------------------------------------------------------------------------------- /docs/Part 3 Efficiency/Chapter 7 Make it cheap/Introduction.md: -------------------------------------------------------------------------------- 1 | # Chapter 7: Make it cheap 2 | 3 | Code efficiency today is often treated indulgently. To a certain degree, it is reasonable. Memory is cheap and developers are expensive. Though if your application is running on millions of devices, it consumes a lot of energy, and some optimization of battery use might save enough energy to power a small city. Or maybe your company is paying lots of money for servers and their maintenance, and some optimization might make it significantly cheaper. Or maybe your application works well for a small number of requests but does not scale well and on the day of the trial, it shuts down. Customers remember such situations. 4 | 5 | Efficiency is important in the long term, but optimization is not easy. Premature optimization often does more harm than good. Instead, there are some rules that can help you make more efficient programs nearly painlessly. Those are the cheap wins: they cost nearly nothing, but still, they can help us improve performance significantly. When they are not sufficient, we should use a profiler and optimize performance-critical parts. This is more difficult to achieve because it requires more understanding of what is expensive and how some optimizations can be done. 6 | 7 | This and the next chapter are about performance: 8 | 9 | - *Chapter 7: Make it cheap* - more general suggestions for performance. 10 | - *Chapter 8: Efficient collection processing - concentrates on collection processing.* 11 | 12 | They focus on general rules to cheaply optimize every-day development. But they also give some Kotlin-specific suggestions on how performance might be optimized in the critical parts of your program. They should also deepen your understanding of thinking about performance in general. 13 | 14 | Please, remember that when there is a tradeoff between readability and performance, you need to answer yourself what is more important in the component you develop. I included some suggestions, but there is no universal answer. -------------------------------------------------------------------------------- /docs/Part 3 Efficiency/Chapter 8 Efficient collection processing/Introduction.md: -------------------------------------------------------------------------------- 1 | # Chapter 8: Efficient collection processing 2 | 3 | Collections are one of the most important concepts in programming. In iOS, one of the most important view elements, `UICollectionView`, is designed to represent a collection. Similarly, in Android, it is hard to imagine an application without `RecyclerView` or `ListView`. When you need to write a portal with news, you will have a list of news. Each of them will probably have a list of authors and a list of tags. When you make an online shop, you start from a list of products. They will most likely have a list of categories and a list of different variants. When a user buys, they use some basket which is probably a collection of products and amounts. Then they need to choose from a list of delivery options and a list of payment methods. In some languages, `String` is just a list of characters. Collections are everywhere in programming! Just think about your application and you will quickly see lots of collections. 4 | 5 | This fact can be reflected also in programming languages. Most modern languages have some collection literals: 6 | 7 | ``` python 8 | 1 // Python 9 | 2 primes = [2, 3, 5, 7, 13] 10 | 3 // Swift 11 | 4 let primes = [2, 3, 5, 7, 13] 12 | ``` 13 | 14 | Good collection processing was a flag functionality of functional programming languages. Name of the Lisp programming language[1](chap65.xhtml#fn-footnote_80_note) stands for “list processing”. Most modern languages have good support for collection processing. This statement includes Kotlin which has one of the most powerful sets of tools for collection processing. Just think of the following collection processing: 15 | 16 | ``` kotlin 17 | val visibleNews = mutableListOf() 18 | for (n in news) { 19 | if(n.visible) { 20 | visibleNews.add(n) 21 | } 22 | } 23 | 24 | Collections.sort(visibleNews, 25 | { n1, n2 -> n2.publishedAt - n1.publishedAt }) 26 | val newsItemAdapters = mutableListOf() 27 | for (n in visibleNews) { 28 | newsItemAdapters.add(NewsItemAdapter(n)) 29 | } 30 | ``` 31 | 32 | In Kotlin it can be replaced with the following notation: 33 | 34 | ``` kotlin 35 | val newsItemAdapters = news 36 | .filter { it.visible } 37 | .sortedByDescending { it.publishedAt } 38 | .map(::NewsItemAdapter) 39 | ``` 40 | 41 | Such notation is not only shorter, but it is also more readable. Every step makes a concrete transformation on the list of elements. Here is a visualization of the above processing: 42 | 43 | ![](../../assets/chapter8/chapter8-1.png) 44 | 45 | The performance of the above examples is very similar. It is not always so simple though. Kotlin has a lot of collection processing methods and we can do the same processing in a lot of different ways. For instance, the below processing implementations have the same result but different performance: 46 | 47 | ``` kotlin 48 | fun productsListProcessing(): String { 49 | return clientsList 50 | .filter { it.adult } 51 | .flatMap { it.products } 52 | .filter { it.bought } 53 | .map { it.price } 54 | .filterNotNull() 55 | .map { "$$it" } 56 | .joinToString(separator = " + ") 57 | } 58 | 59 | fun productsSequenceProcessing(): String { 60 | return clientsList.asSequence() 61 | .filter { it.adult } 62 | .flatMap { it.products.asSequence() } 63 | .filter { it.bought } 64 | .mapNotNull { it.price } 65 | .joinToString(separator = " + ") { "$$it" } 66 | } 67 | ``` 68 | 69 | Collection processing optimization is much more than just a brain-puzzle. Collection processing is extremely important and, in big systems, often performance-critical. This is why it is often key to improve the performance of our program. Especially if we do backend application development or data analysis. Although, when we are implementing front-end clients, we can face collection processing that limits the performance of our application as well. As a consultant, I’ve seen a lot of projects and my experience is that I see collection processing again and again in lots of different places. This is not something that can be ignored so easily. 70 | 71 | The good news is that collection processing optimization is not hard to master. There are some rules and a few things to remember but actually, anyone can do it effectively. This is what we are going to learn in this chapter. -------------------------------------------------------------------------------- /docs/Part 3 Efficiency/Chapter 8 Efficient collection processing/Item 50 Limit the number of operations.md: -------------------------------------------------------------------------------- 1 | ## 第50条: 减少操作的次数 2 | 3 | 每个集合处理的方法都伴随着性能开销。对于标准库中提供的集合处理方法,在底层实现上,通常是对集合元素的一次迭代和新集合的创建。而对于序列处理,它会创建一个新的对象来持有和操作整个序列。尤其是当处理的元素数量很多的时候,这两者的性能开销都会变的很大。因此,我们应该减少处理步骤的数量,主要可以通过使用复合操作来做到这一点。例如,我们使用 `filterNotNull`,而不是先过滤非空类型然后再转换为不可空类型。或者我们可以只使用`mapNotNull`而不是先进行映射之后再进行过滤操作。 4 | 5 | ``` kotlin 6 | class Student(val name: String?) 7 | 8 | // Works 9 | fun List.getNames(): List = this 10 | .map { it.name } 11 | .filter { it != null } 12 | .map { it!! } 13 | 14 | // Better 15 | fun List.getNames(): List = this 16 | .map { it.name } 17 | .filterNotNull() 18 | 19 | // Best 20 | fun List.getNames(): List = this 21 | .mapNotNull { it.name } 22 | ``` 23 | 24 | 通常来说,最大的问题不是缺乏对这一问题的认知,而是缺乏对应该使用哪些集合处理函数的了解。 这是学习它们很好的另一个原因。 此外,IDE也会提供有用的警告,这些警告经常给我们更好的替代方案的建议。 25 | 26 | ![](../../assets/chapter8/chapter8-6.png) 27 | 28 | 尽管如此,了解如何替代复合操作还是很有必要的。 下面列出了一些常见的函数调用和限制操作数量的替代方法: 29 | 30 | ![](../../assets/chapter8/chapter8-7.png) 31 | 32 | ### 总结 33 | 34 | 大多数集合处理步骤需要迭代整个集合和额外的集合创建。 这个成本可以通过使用更合适的函数来限制。 -------------------------------------------------------------------------------- /docs/Part 3 Efficiency/Chapter 8 Efficient collection processing/Item 51 Consider Arrays with primitives for performance-critical processing.md: -------------------------------------------------------------------------------- 1 | ## 第51条:在“性能优先”的场景,使用基础类型数组 2 | 3 | 我们不能在Kotlin中声明基础类型,但是作为一种优化手段,在底层经常会使用它。这是一个重要的优化,在*第45项:避免不必要的对象创建*中已经讲述过。基础类型是: 4 | 5 | - 更轻量级的,因为每个对象都会增加额外的内存开销 6 | - 更快,因为通过getter方法访问值有额外的性能开销 7 | 8 | 因此,对量较大的数据使用基础类型可能是一个重要的优化。但有一个问题是,在Kotlin中,典型的集合,如`List`或`Set`,使用的是泛型类型。基础类型不能作为泛型参数使用,因此我们最终使用的是包装类型。这是一个方便的解决方案,适合大多数情况,因为在标准集合上更容易做处理操作。尽管如此,在代码中对性能有重要要求的部分,我们应该考虑使用基础类型数组,比如`IntArray`或`LongArray`,因为它们的内存占用更少,处理效率更高。 9 | 10 | | Kotlin type | Java type | 11 | | :---------- | :------------ | 12 | | Int | int | 13 | | List | List | 14 | | Array | Integer[] | 15 | | IntArray | int[] | 16 | 17 | 基础类型数组有多轻量级呢? 假设在Kotlin/JVM中,我们需要保存1 000 000个整数,我们可以选择将它们保存在`IntArray`或`List`中。当你对他们的内存情况进行计算时,你会发现`IntArray`分配了4 000 016字节的内存,而`List`分配了20 000 040字节,足足5倍的差距。 如果你需要做内存优化,并且你的集合持有的数据类型是基础类型, 如`Int`,那么你可以尝试使用基础类型数组来优化。 18 | 19 | ``` kotlin 20 | import jdk.nashorn.internal.ir.debug.ObjectSizeCalculator.getObjectSize 21 | 22 | fun main() { 23 | val ints = List(1_000_000) { it } 24 | val array: Array = ints.toTypedArray() 25 | val intArray: IntArray = ints.toIntArray() 26 | println(getObjectSize(ints)) // 20 000 040 27 | println(getObjectSize(array)) // 20 000 016 28 | println(getObjectSize(intArray)) // 4 000 016 29 | } 30 | ``` 31 | 32 | 在性能上同样也有差别,对于同样的1 000 000个数字的集合,在计算平均值时,对基础类型数组的处理要快25%左右。 33 | 34 | ``` kotlin 35 | open class InlineFilterBenchmark { 36 | 37 | lateinit var list: List 38 | lateinit var array: IntArray 39 | 40 | @Setup 41 | fun init() { 42 | list = List(1_000_000) { it } 43 | array = IntArray(1_000_000) { it } 44 | } 45 | 46 | @Benchmark 47 | // On average 1 260 593 ns 48 | fun averageOnIntList(): Double { 49 | return list.average() 50 | } 51 | 52 | @Benchmark 53 | // On average 868 509 ns 54 | fun averageOnIntArray(): Double { 55 | return array.average() 56 | } 57 | } 58 | ``` 59 | 60 | 正如你所看到的,使用基础类型和基础类型数组可以作为代码中优化性能的一种手段。它们占用的内存更少,处理速度也更快。虽然在大多数情况下,这种优化带来的效果并不明显,不足以让我们默认使用基础类型数组而不是`List`。`List`更直观,使用频率更高,所以在大多数情况下,我们应该使用它。但你仍需记住这种优化,以防你需要优化一些”性能优先“的部分。 61 | 62 | ### 总结 63 | 64 | 在一般情况下,应该优先使用`List`或`Set`而不是`Array`。但如果你需要保存大量的基础类型的数据,使用`Array`可能会显著提高你的性能和内存使用情况。这一条特别适用于开发基础库或编写游戏或高级图形处理的开发者。 -------------------------------------------------------------------------------- /docs/Part 3 Efficiency/Chapter 8 Efficient collection processing/Item 52 Consider using mutable collections.md: -------------------------------------------------------------------------------- 1 | ## 第52条:在处理局部变量时,考虑使用可变集合 2 | 3 | 使用可变集合而不是不可变集合的最大优势是,它们在性能上表现更好。当我们向一个不可变的集合添加一个元素时,我们需要创建一个新的集合并将所有的元素添加到其中。下面是目前在Kotlin stdlib(Kotlin 1.2)中的实现方式: 4 | 5 | ``` kotlin 6 | operator fun Iterable.plus(element: T): List { 7 | if (this is Collection) return this.plus(element) 8 | val result = ArrayList() 9 | result.addAll(this) 10 | result.add(element) 11 | return result 12 | } 13 | ``` 14 | 15 | 当我们处理数据量较大的集合时,添加前一个集合中的所有元素是一个巨大的性能开销。这就是为什么使用可变集合是一种性能优化,特别是当我们需要添加元素时。另一方面,*第1条:限制可变性*告诉我们使用不可变的集合来保证安全的好处。不过请注意,这些论点很少适用于局部变量,因为局部变量很少需要同步或封装。这就是为什么对于局部变量处理来说,通常使用可变集合更有意义。这一论点可以在标准库中得到证实,所有的集合处理函数在内部都是使用可变集合实现的: 16 | 17 | ``` kotlin 18 | inline fun Iterable.map( 19 | transform: (T) -> R 20 | ): List { 21 | val size = if (this is Collection<*>) this.size else 10 22 | val destination = ArrayList(size) 23 | for (item in this) 24 | destination.add(transform(item)) 25 | return destination 26 | } 27 | ``` 28 | 29 | 而不是使用不可变的集合: 30 | 31 | ``` kotlin 32 | // This is not how map is implemented 33 | inline fun Iterable.map( 34 | transform: (T) -> R 35 | ): List { 36 | var destination = listOf() 37 | for (item in this) 38 | destination += transform(item) 39 | return destination 40 | } 41 | ``` 42 | 43 | ### 总结 44 | 45 | 使用可变集合来添加元素通常有更好的性能表现,但是不可变集合给了我们更多的控制权来控制它们如何被更改。但是在局部变量范围内,我们通常不需要这种控制,所以应首选可变集合。特别是在工具类中,元素的插入可能会发生很多次。 -------------------------------------------------------------------------------- /docs/assets/chapter1/chapter1-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxzMeng/Effective-Kotlin-zh-CN/750403009f684b89fd04c2a07bbc946516d2de18/docs/assets/chapter1/chapter1-1.png -------------------------------------------------------------------------------- /docs/assets/chapter1/chapter1-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxzMeng/Effective-Kotlin-zh-CN/750403009f684b89fd04c2a07bbc946516d2de18/docs/assets/chapter1/chapter1-2.png -------------------------------------------------------------------------------- /docs/assets/chapter1/chapter1-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxzMeng/Effective-Kotlin-zh-CN/750403009f684b89fd04c2a07bbc946516d2de18/docs/assets/chapter1/chapter1-3.png -------------------------------------------------------------------------------- /docs/assets/chapter2/chapter2-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxzMeng/Effective-Kotlin-zh-CN/750403009f684b89fd04c2a07bbc946516d2de18/docs/assets/chapter2/chapter2-1.png -------------------------------------------------------------------------------- /docs/assets/chapter2/chapter2-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxzMeng/Effective-Kotlin-zh-CN/750403009f684b89fd04c2a07bbc946516d2de18/docs/assets/chapter2/chapter2-2.png -------------------------------------------------------------------------------- /docs/assets/chapter3/chapter3-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxzMeng/Effective-Kotlin-zh-CN/750403009f684b89fd04c2a07bbc946516d2de18/docs/assets/chapter3/chapter3-1.png -------------------------------------------------------------------------------- /docs/assets/chapter3/chapter3-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxzMeng/Effective-Kotlin-zh-CN/750403009f684b89fd04c2a07bbc946516d2de18/docs/assets/chapter3/chapter3-2.png -------------------------------------------------------------------------------- /docs/assets/chapter3/chapter3-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxzMeng/Effective-Kotlin-zh-CN/750403009f684b89fd04c2a07bbc946516d2de18/docs/assets/chapter3/chapter3-3.png -------------------------------------------------------------------------------- /docs/assets/chapter3/chapter3-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxzMeng/Effective-Kotlin-zh-CN/750403009f684b89fd04c2a07bbc946516d2de18/docs/assets/chapter3/chapter3-4.png -------------------------------------------------------------------------------- /docs/assets/chapter3/chapter3-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxzMeng/Effective-Kotlin-zh-CN/750403009f684b89fd04c2a07bbc946516d2de18/docs/assets/chapter3/chapter3-5.png -------------------------------------------------------------------------------- /docs/assets/chapter3/chapter3-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxzMeng/Effective-Kotlin-zh-CN/750403009f684b89fd04c2a07bbc946516d2de18/docs/assets/chapter3/chapter3-6.png -------------------------------------------------------------------------------- /docs/assets/chapter3/chapter3-7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxzMeng/Effective-Kotlin-zh-CN/750403009f684b89fd04c2a07bbc946516d2de18/docs/assets/chapter3/chapter3-7.png -------------------------------------------------------------------------------- /docs/assets/chapter3/chapter3-8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxzMeng/Effective-Kotlin-zh-CN/750403009f684b89fd04c2a07bbc946516d2de18/docs/assets/chapter3/chapter3-8.png -------------------------------------------------------------------------------- /docs/assets/chapter3/chapter3-9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxzMeng/Effective-Kotlin-zh-CN/750403009f684b89fd04c2a07bbc946516d2de18/docs/assets/chapter3/chapter3-9.png -------------------------------------------------------------------------------- /docs/assets/chapter4/chapter4-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxzMeng/Effective-Kotlin-zh-CN/750403009f684b89fd04c2a07bbc946516d2de18/docs/assets/chapter4/chapter4-1.png -------------------------------------------------------------------------------- /docs/assets/chapter4/chapter4-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxzMeng/Effective-Kotlin-zh-CN/750403009f684b89fd04c2a07bbc946516d2de18/docs/assets/chapter4/chapter4-2.png -------------------------------------------------------------------------------- /docs/assets/chapter4/chapter4-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxzMeng/Effective-Kotlin-zh-CN/750403009f684b89fd04c2a07bbc946516d2de18/docs/assets/chapter4/chapter4-3.png -------------------------------------------------------------------------------- /docs/assets/chapter4/chapter4-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxzMeng/Effective-Kotlin-zh-CN/750403009f684b89fd04c2a07bbc946516d2de18/docs/assets/chapter4/chapter4-4.png -------------------------------------------------------------------------------- /docs/assets/chapter4/chapter4-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxzMeng/Effective-Kotlin-zh-CN/750403009f684b89fd04c2a07bbc946516d2de18/docs/assets/chapter4/chapter4-5.png -------------------------------------------------------------------------------- /docs/assets/chapter4/chapter4-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxzMeng/Effective-Kotlin-zh-CN/750403009f684b89fd04c2a07bbc946516d2de18/docs/assets/chapter4/chapter4-6.png -------------------------------------------------------------------------------- /docs/assets/chapter4/chapter4-7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxzMeng/Effective-Kotlin-zh-CN/750403009f684b89fd04c2a07bbc946516d2de18/docs/assets/chapter4/chapter4-7.png -------------------------------------------------------------------------------- /docs/assets/chapter4/chapter4-8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxzMeng/Effective-Kotlin-zh-CN/750403009f684b89fd04c2a07bbc946516d2de18/docs/assets/chapter4/chapter4-8.png -------------------------------------------------------------------------------- /docs/assets/chapter4/chapter4-9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxzMeng/Effective-Kotlin-zh-CN/750403009f684b89fd04c2a07bbc946516d2de18/docs/assets/chapter4/chapter4-9.png -------------------------------------------------------------------------------- /docs/assets/chapter5/chapter5-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxzMeng/Effective-Kotlin-zh-CN/750403009f684b89fd04c2a07bbc946516d2de18/docs/assets/chapter5/chapter5-1.png -------------------------------------------------------------------------------- /docs/assets/chapter5/chapter5-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxzMeng/Effective-Kotlin-zh-CN/750403009f684b89fd04c2a07bbc946516d2de18/docs/assets/chapter5/chapter5-2.png -------------------------------------------------------------------------------- /docs/assets/chapter5/chapter5-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxzMeng/Effective-Kotlin-zh-CN/750403009f684b89fd04c2a07bbc946516d2de18/docs/assets/chapter5/chapter5-3.png -------------------------------------------------------------------------------- /docs/assets/chapter6/chapter6-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxzMeng/Effective-Kotlin-zh-CN/750403009f684b89fd04c2a07bbc946516d2de18/docs/assets/chapter6/chapter6-1.png -------------------------------------------------------------------------------- /docs/assets/chapter6/chapter6-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxzMeng/Effective-Kotlin-zh-CN/750403009f684b89fd04c2a07bbc946516d2de18/docs/assets/chapter6/chapter6-2.png -------------------------------------------------------------------------------- /docs/assets/chapter6/chapter6-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxzMeng/Effective-Kotlin-zh-CN/750403009f684b89fd04c2a07bbc946516d2de18/docs/assets/chapter6/chapter6-3.png -------------------------------------------------------------------------------- /docs/assets/chapter6/chapter6-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxzMeng/Effective-Kotlin-zh-CN/750403009f684b89fd04c2a07bbc946516d2de18/docs/assets/chapter6/chapter6-4.png -------------------------------------------------------------------------------- /docs/assets/chapter6/chapter6-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxzMeng/Effective-Kotlin-zh-CN/750403009f684b89fd04c2a07bbc946516d2de18/docs/assets/chapter6/chapter6-5.png -------------------------------------------------------------------------------- /docs/assets/chapter7/chapter7-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxzMeng/Effective-Kotlin-zh-CN/750403009f684b89fd04c2a07bbc946516d2de18/docs/assets/chapter7/chapter7-1.png -------------------------------------------------------------------------------- /docs/assets/chapter7/chapter7-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxzMeng/Effective-Kotlin-zh-CN/750403009f684b89fd04c2a07bbc946516d2de18/docs/assets/chapter7/chapter7-2.png -------------------------------------------------------------------------------- /docs/assets/chapter8/chapter8-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxzMeng/Effective-Kotlin-zh-CN/750403009f684b89fd04c2a07bbc946516d2de18/docs/assets/chapter8/chapter8-1.png -------------------------------------------------------------------------------- /docs/assets/chapter8/chapter8-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxzMeng/Effective-Kotlin-zh-CN/750403009f684b89fd04c2a07bbc946516d2de18/docs/assets/chapter8/chapter8-2.png -------------------------------------------------------------------------------- /docs/assets/chapter8/chapter8-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxzMeng/Effective-Kotlin-zh-CN/750403009f684b89fd04c2a07bbc946516d2de18/docs/assets/chapter8/chapter8-3.png -------------------------------------------------------------------------------- /docs/assets/chapter8/chapter8-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxzMeng/Effective-Kotlin-zh-CN/750403009f684b89fd04c2a07bbc946516d2de18/docs/assets/chapter8/chapter8-4.png -------------------------------------------------------------------------------- /docs/assets/chapter8/chapter8-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxzMeng/Effective-Kotlin-zh-CN/750403009f684b89fd04c2a07bbc946516d2de18/docs/assets/chapter8/chapter8-5.png -------------------------------------------------------------------------------- /docs/assets/chapter8/chapter8-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxzMeng/Effective-Kotlin-zh-CN/750403009f684b89fd04c2a07bbc946516d2de18/docs/assets/chapter8/chapter8-6.png -------------------------------------------------------------------------------- /docs/assets/chapter8/chapter8-7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxzMeng/Effective-Kotlin-zh-CN/750403009f684b89fd04c2a07bbc946516d2de18/docs/assets/chapter8/chapter8-7.png -------------------------------------------------------------------------------- /docs/gitbook/fonts/fontawesome/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxzMeng/Effective-Kotlin-zh-CN/750403009f684b89fd04c2a07bbc946516d2de18/docs/gitbook/fonts/fontawesome/FontAwesome.otf -------------------------------------------------------------------------------- /docs/gitbook/fonts/fontawesome/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxzMeng/Effective-Kotlin-zh-CN/750403009f684b89fd04c2a07bbc946516d2de18/docs/gitbook/fonts/fontawesome/fontawesome-webfont.eot -------------------------------------------------------------------------------- /docs/gitbook/fonts/fontawesome/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxzMeng/Effective-Kotlin-zh-CN/750403009f684b89fd04c2a07bbc946516d2de18/docs/gitbook/fonts/fontawesome/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /docs/gitbook/fonts/fontawesome/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxzMeng/Effective-Kotlin-zh-CN/750403009f684b89fd04c2a07bbc946516d2de18/docs/gitbook/fonts/fontawesome/fontawesome-webfont.woff -------------------------------------------------------------------------------- /docs/gitbook/fonts/fontawesome/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxzMeng/Effective-Kotlin-zh-CN/750403009f684b89fd04c2a07bbc946516d2de18/docs/gitbook/fonts/fontawesome/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-highlight/ebook.css: -------------------------------------------------------------------------------- 1 | pre, 2 | code { 3 | /* http://jmblog.github.io/color-themes-for-highlightjs */ 4 | /* Tomorrow Comment */ 5 | /* Tomorrow Red */ 6 | /* Tomorrow Orange */ 7 | /* Tomorrow Yellow */ 8 | /* Tomorrow Green */ 9 | /* Tomorrow Aqua */ 10 | /* Tomorrow Blue */ 11 | /* Tomorrow Purple */ 12 | } 13 | pre .hljs-comment, 14 | code .hljs-comment, 15 | pre .hljs-title, 16 | code .hljs-title { 17 | color: #8e908c; 18 | } 19 | pre .hljs-variable, 20 | code .hljs-variable, 21 | pre .hljs-attribute, 22 | code .hljs-attribute, 23 | pre .hljs-tag, 24 | code .hljs-tag, 25 | pre .hljs-regexp, 26 | code .hljs-regexp, 27 | pre .hljs-deletion, 28 | code .hljs-deletion, 29 | pre .ruby .hljs-constant, 30 | code .ruby .hljs-constant, 31 | pre .xml .hljs-tag .hljs-title, 32 | code .xml .hljs-tag .hljs-title, 33 | pre .xml .hljs-pi, 34 | code .xml .hljs-pi, 35 | pre .xml .hljs-doctype, 36 | code .xml .hljs-doctype, 37 | pre .html .hljs-doctype, 38 | code .html .hljs-doctype, 39 | pre .css .hljs-id, 40 | code .css .hljs-id, 41 | pre .css .hljs-class, 42 | code .css .hljs-class, 43 | pre .css .hljs-pseudo, 44 | code .css .hljs-pseudo { 45 | color: #c82829; 46 | } 47 | pre .hljs-number, 48 | code .hljs-number, 49 | pre .hljs-preprocessor, 50 | code .hljs-preprocessor, 51 | pre .hljs-pragma, 52 | code .hljs-pragma, 53 | pre .hljs-built_in, 54 | code .hljs-built_in, 55 | pre .hljs-literal, 56 | code .hljs-literal, 57 | pre .hljs-params, 58 | code .hljs-params, 59 | pre .hljs-constant, 60 | code .hljs-constant { 61 | color: #f5871f; 62 | } 63 | pre .ruby .hljs-class .hljs-title, 64 | code .ruby .hljs-class .hljs-title, 65 | pre .css .hljs-rules .hljs-attribute, 66 | code .css .hljs-rules .hljs-attribute { 67 | color: #eab700; 68 | } 69 | pre .hljs-string, 70 | code .hljs-string, 71 | pre .hljs-value, 72 | code .hljs-value, 73 | pre .hljs-inheritance, 74 | code .hljs-inheritance, 75 | pre .hljs-header, 76 | code .hljs-header, 77 | pre .hljs-addition, 78 | code .hljs-addition, 79 | pre .ruby .hljs-symbol, 80 | code .ruby .hljs-symbol, 81 | pre .xml .hljs-cdata, 82 | code .xml .hljs-cdata { 83 | color: #718c00; 84 | } 85 | pre .css .hljs-hexcolor, 86 | code .css .hljs-hexcolor { 87 | color: #3e999f; 88 | } 89 | pre .hljs-function, 90 | code .hljs-function, 91 | pre .python .hljs-decorator, 92 | code .python .hljs-decorator, 93 | pre .python .hljs-title, 94 | code .python .hljs-title, 95 | pre .ruby .hljs-function .hljs-title, 96 | code .ruby .hljs-function .hljs-title, 97 | pre .ruby .hljs-title .hljs-keyword, 98 | code .ruby .hljs-title .hljs-keyword, 99 | pre .perl .hljs-sub, 100 | code .perl .hljs-sub, 101 | pre .javascript .hljs-title, 102 | code .javascript .hljs-title, 103 | pre .coffeescript .hljs-title, 104 | code .coffeescript .hljs-title { 105 | color: #4271ae; 106 | } 107 | pre .hljs-keyword, 108 | code .hljs-keyword, 109 | pre .javascript .hljs-function, 110 | code .javascript .hljs-function { 111 | color: #8959a8; 112 | } 113 | pre .hljs, 114 | code .hljs { 115 | display: block; 116 | background: white; 117 | color: #4d4d4c; 118 | padding: 0.5em; 119 | } 120 | pre .coffeescript .javascript, 121 | code .coffeescript .javascript, 122 | pre .javascript .xml, 123 | code .javascript .xml, 124 | pre .tex .hljs-formula, 125 | code .tex .hljs-formula, 126 | pre .xml .javascript, 127 | code .xml .javascript, 128 | pre .xml .vbscript, 129 | code .xml .vbscript, 130 | pre .xml .css, 131 | code .xml .css, 132 | pre .xml .hljs-cdata, 133 | code .xml .hljs-cdata { 134 | opacity: 0.5; 135 | } 136 | -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-lunr/search-lunr.js: -------------------------------------------------------------------------------- 1 | require([ 2 | 'gitbook', 3 | 'jquery' 4 | ], function(gitbook, $) { 5 | // Define global search engine 6 | function LunrSearchEngine() { 7 | this.index = null; 8 | this.store = {}; 9 | this.name = 'LunrSearchEngine'; 10 | } 11 | 12 | // Initialize lunr by fetching the search index 13 | LunrSearchEngine.prototype.init = function() { 14 | var that = this; 15 | var d = $.Deferred(); 16 | 17 | $.getJSON(gitbook.state.basePath+'/search_index.json') 18 | .then(function(data) { 19 | // eslint-disable-next-line no-undef 20 | that.index = lunr.Index.load(data.index); 21 | that.store = data.store; 22 | d.resolve(); 23 | }); 24 | 25 | return d.promise(); 26 | }; 27 | 28 | // Search for a term and return results 29 | LunrSearchEngine.prototype.search = function(q, offset, length) { 30 | var that = this; 31 | var results = []; 32 | 33 | if (this.index) { 34 | results = $.map(this.index.search(q), function(result) { 35 | var doc = that.store[result.ref]; 36 | 37 | return { 38 | title: doc.title, 39 | url: doc.url, 40 | body: doc.summary || doc.body 41 | }; 42 | }); 43 | } 44 | 45 | return $.Deferred().resolve({ 46 | query: q, 47 | results: results.slice(0, length), 48 | count: results.length 49 | }).promise(); 50 | }; 51 | 52 | // Set gitbook research 53 | gitbook.events.bind('start', function(e, config) { 54 | var engine = gitbook.search.getEngine(); 55 | if (!engine) { 56 | gitbook.search.setEngine(LunrSearchEngine, config); 57 | } 58 | }); 59 | }); 60 | -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-search/search-engine.js: -------------------------------------------------------------------------------- 1 | require([ 2 | 'gitbook', 3 | 'jquery' 4 | ], function(gitbook, $) { 5 | // Global search objects 6 | var engine = null; 7 | var initialized = false; 8 | 9 | // Set a new search engine 10 | function setEngine(Engine, config) { 11 | initialized = false; 12 | engine = new Engine(config); 13 | 14 | init(config); 15 | } 16 | 17 | // Initialize search engine with config 18 | function init(config) { 19 | if (!engine) throw new Error('No engine set for research. Set an engine using gitbook.research.setEngine(Engine).'); 20 | 21 | return engine.init(config) 22 | .then(function() { 23 | initialized = true; 24 | gitbook.events.trigger('search.ready'); 25 | }); 26 | } 27 | 28 | // Launch search for query q 29 | function query(q, offset, length) { 30 | if (!initialized) throw new Error('Search has not been initialized'); 31 | return engine.search(q, offset, length); 32 | } 33 | 34 | // Get stats about search 35 | function getEngine() { 36 | return engine? engine.name : null; 37 | } 38 | 39 | function isInitialized() { 40 | return initialized; 41 | } 42 | 43 | // Initialize gitbook.search 44 | gitbook.search = { 45 | setEngine: setEngine, 46 | getEngine: getEngine, 47 | query: query, 48 | isInitialized: isInitialized 49 | }; 50 | }); -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-search/search.css: -------------------------------------------------------------------------------- 1 | /* 2 | This CSS only styled the search results section, not the search input 3 | It defines the basic interraction to hide content when displaying results, etc 4 | */ 5 | #book-search-results .search-results { 6 | display: none; 7 | } 8 | #book-search-results .search-results ul.search-results-list { 9 | list-style-type: none; 10 | padding-left: 0; 11 | } 12 | #book-search-results .search-results ul.search-results-list li { 13 | margin-bottom: 1.5rem; 14 | padding-bottom: 0.5rem; 15 | /* Highlight results */ 16 | } 17 | #book-search-results .search-results ul.search-results-list li p em { 18 | background-color: rgba(255, 220, 0, 0.4); 19 | font-style: normal; 20 | } 21 | #book-search-results .search-results .no-results { 22 | display: none; 23 | } 24 | #book-search-results.open .search-results { 25 | display: block; 26 | } 27 | #book-search-results.open .search-noresults { 28 | display: none; 29 | } 30 | #book-search-results.no-results .search-results .has-results { 31 | display: none; 32 | } 33 | #book-search-results.no-results .search-results .no-results { 34 | display: block; 35 | } 36 | -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-sharing/buttons.js: -------------------------------------------------------------------------------- 1 | require(['gitbook', 'jquery'], function(gitbook, $) { 2 | var SITES = { 3 | 'facebook': { 4 | 'label': 'Facebook', 5 | 'icon': 'fa fa-facebook', 6 | 'onClick': function(e) { 7 | e.preventDefault(); 8 | window.open('http://www.facebook.com/sharer/sharer.php?s=100&p[url]='+encodeURIComponent(location.href)); 9 | } 10 | }, 11 | 'twitter': { 12 | 'label': 'Twitter', 13 | 'icon': 'fa fa-twitter', 14 | 'onClick': function(e) { 15 | e.preventDefault(); 16 | window.open('http://twitter.com/home?status='+encodeURIComponent(document.title+' '+location.href)); 17 | } 18 | }, 19 | 'google': { 20 | 'label': 'Google+', 21 | 'icon': 'fa fa-google-plus', 22 | 'onClick': function(e) { 23 | e.preventDefault(); 24 | window.open('https://plus.google.com/share?url='+encodeURIComponent(location.href)); 25 | } 26 | }, 27 | 'weibo': { 28 | 'label': 'Weibo', 29 | 'icon': 'fa fa-weibo', 30 | 'onClick': function(e) { 31 | e.preventDefault(); 32 | window.open('http://service.weibo.com/share/share.php?content=utf-8&url='+encodeURIComponent(location.href)+'&title='+encodeURIComponent(document.title)); 33 | } 34 | }, 35 | 'instapaper': { 36 | 'label': 'Instapaper', 37 | 'icon': 'fa fa-instapaper', 38 | 'onClick': function(e) { 39 | e.preventDefault(); 40 | window.open('http://www.instapaper.com/text?u='+encodeURIComponent(location.href)); 41 | } 42 | }, 43 | 'vk': { 44 | 'label': 'VK', 45 | 'icon': 'fa fa-vk', 46 | 'onClick': function(e) { 47 | e.preventDefault(); 48 | window.open('http://vkontakte.ru/share.php?url='+encodeURIComponent(location.href)); 49 | } 50 | } 51 | }; 52 | 53 | 54 | 55 | gitbook.events.bind('start', function(e, config) { 56 | var opts = config.sharing; 57 | 58 | // Create dropdown menu 59 | var menu = $.map(opts.all, function(id) { 60 | var site = SITES[id]; 61 | 62 | return { 63 | text: site.label, 64 | onClick: site.onClick 65 | }; 66 | }); 67 | 68 | // Create main button with dropdown 69 | if (menu.length > 0) { 70 | gitbook.toolbar.createButton({ 71 | icon: 'fa fa-share-alt', 72 | label: 'Share', 73 | position: 'right', 74 | dropdown: [menu] 75 | }); 76 | } 77 | 78 | // Direct actions to share 79 | $.each(SITES, function(sideId, site) { 80 | if (!opts[sideId]) return; 81 | 82 | gitbook.toolbar.createButton({ 83 | icon: site.icon, 84 | label: site.text, 85 | position: 'right', 86 | onClick: site.onClick 87 | }); 88 | }); 89 | }); 90 | }); 91 | -------------------------------------------------------------------------------- /docs/gitbook/images/apple-touch-icon-precomposed-152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxzMeng/Effective-Kotlin-zh-CN/750403009f684b89fd04c2a07bbc946516d2de18/docs/gitbook/images/apple-touch-icon-precomposed-152.png -------------------------------------------------------------------------------- /docs/gitbook/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxzMeng/Effective-Kotlin-zh-CN/750403009f684b89fd04c2a07bbc946516d2de18/docs/gitbook/images/favicon.ico -------------------------------------------------------------------------------- /part-1-good-code/README.md: -------------------------------------------------------------------------------- 1 | # Part 1 Good Code 2 | 3 | -------------------------------------------------------------------------------- /part-1-good-code/chapter-1-safety/README.md: -------------------------------------------------------------------------------- 1 | # Chapter 1 Safety 2 | 3 | -------------------------------------------------------------------------------- /part-1-good-code/chapter-1-safety/di-1-tiao-xian-zhi-ke-bian-xing-part-1-good-codechapter-1-safetyitem-1-limit-mutability.md.md: -------------------------------------------------------------------------------- 1 | # \[第1条:限制可变性]\(Part 1 Good code/Chapter 1 Safety/Item 1 Limit mutability.md) 2 | 3 | -------------------------------------------------------------------------------- /part-1-good-code/chapter-1-safety/di-10-tiao-bian-xie-dan-yuan-ce-shi-part-1-good-codechapter-1-safetyitem-10-write-unit-tests.md.md: -------------------------------------------------------------------------------- 1 | # \[第10条:编写单元测试]\(Part 1 Good code/Chapter 1 Safety/Item 10 Write unit tests.md) 2 | 3 | -------------------------------------------------------------------------------- /part-1-good-code/chapter-1-safety/di-2-tiao-zui-xiao-hua-bian-liang-zuo-yong-yu-part-1-good-codechapter-1-safetyitem-2-minimize-the-sc.md: -------------------------------------------------------------------------------- 1 | # \[第2条:最小化变量作用域]\(Part 1 Good code/Chapter 1 Safety/Item 2 Minimize the scope of variables.md) 2 | 3 | -------------------------------------------------------------------------------- /part-1-good-code/chapter-1-safety/di-3-tiao-jin-kuai-xiao-chu-ping-tai-lei-xing-part-1-good-codechapter-1-safetyitem-3-eliminate-platf.md: -------------------------------------------------------------------------------- 1 | # \[第3条:尽快消除平台类型]\(Part 1 Good code/Chapter 1 Safety/Item 3 Eliminate platform types as soon as possible.md) 2 | 3 | -------------------------------------------------------------------------------- /part-1-good-code/chapter-1-safety/di-4-tiao-bu-yao-ba-tui-duan-lei-xing-bao-lou-gei-wai-bu-part-1-good-codechapter-1-safetyitem-4-do-n.md: -------------------------------------------------------------------------------- 1 | # \[第4条:不要把推断类型暴露给外部]\(Part 1 Good code/Chapter 1 Safety/Item 4 Do not expose inferred types.md) 2 | 3 | -------------------------------------------------------------------------------- /part-1-good-code/chapter-1-safety/di-5-tiao-zai-can-shu-yu-zhuang-tai-shang-zhi-ding-ni-de-qi-wang-part-1-good-codechapter-1-safetyite.md: -------------------------------------------------------------------------------- 1 | # \[第5条:在参数与状态上指定你的期望]\(Part 1 Good code/Chapter 1 Safety/Item 5 Specify your expectations on arguments and state.md) 2 | 3 | -------------------------------------------------------------------------------- /part-1-good-code/chapter-1-safety/di-6-tiao-jin-ke-neng-shi-yong-biao-zhun-ku-zhong-ti-gong-de-yi-chang-part-1-good-codechapter-1-safe.md: -------------------------------------------------------------------------------- 1 | # \[第6条:尽可能使用标准库中提供的异常]\(Part 1 Good code/Chapter 1 Safety/Item 6 Prefer standard errors to custom ones.md) 2 | 3 | -------------------------------------------------------------------------------- /part-1-good-code/chapter-1-safety/di-7-tiao-dang-bu-neng-fan-hui-yu-qi-jie-guo-shi-you-xian-shi-yong-nullohuo-failure-zuo-wei-fan-hui.md: -------------------------------------------------------------------------------- 1 | # \[第7条:当不能返回预期结果时,优先使用null o或Failure 作为返回值]\(Part 1 Good code/Chapter 1 Safety/Item 7 Prefer null or Failure result when the lack of result is possible.md) 2 | 3 | -------------------------------------------------------------------------------- /part-1-good-code/chapter-1-safety/di-8-tiao-zheng-que-di-chu-li-null-zhi-part-1-good-codechapter-1-safetyitem-8-handle-nulls-properly..md: -------------------------------------------------------------------------------- 1 | # \[第8条:正确地处理null值]\(Part 1 Good code/Chapter 1 Safety/Item 8 Handle nulls properly.md) 2 | 3 | -------------------------------------------------------------------------------- /part-1-good-code/chapter-1-safety/di-9-tiao-shi-yong-use-guan-bi-zi-yuan-part-1-good-codechapter-1-safetyitem-9-close-resources-with-u.md: -------------------------------------------------------------------------------- 1 | # \[第9条:使用use关闭资源]\(Part 1 Good code/Chapter 1 Safety/Item 9 Close resources with use.md) 2 | 3 | -------------------------------------------------------------------------------- /part-1-good-code/chapter-1-safety/yin-yan-part-1-good-codechapter-1-safetyintroduction.md.md: -------------------------------------------------------------------------------- 1 | # \[引言]\(Part 1 Good code/Chapter 1 Safety/Introduction.md) 2 | 3 | -------------------------------------------------------------------------------- /part-1-good-code/chapter-2-readability/README.md: -------------------------------------------------------------------------------- 1 | # Chapter 2 Readability 2 | 3 | -------------------------------------------------------------------------------- /part-1-good-code/chapter-2-readability/di-11-tiao-ke-du-xing-she-ji-part-1-good-codechapter-2-readabilityitem-11-design-for-readability.md.md: -------------------------------------------------------------------------------- 1 | # \[第11条:可读性设计]\(Part 1 Good code/Chapter 2 Readability/Item 11 Design for readability.md) 2 | 3 | -------------------------------------------------------------------------------- /part-1-good-code/chapter-2-readability/di-12-tiao-cao-zuo-fu-de-han-yi-ying-yu-qi-han-shu-ming-yi-zhi-part-1-good-codechapter-2-readability.md: -------------------------------------------------------------------------------- 1 | # \[第12条:操作符的含义应与其函数名一致]\(Part 1 Good code/Chapter 2 Readability/Item 12 Operator meaning should be consistent with its function name.md) 2 | 3 | -------------------------------------------------------------------------------- /part-1-good-code/chapter-2-readability/di-13-tiao-bi-mian-fan-hui-huo-cao-zuo-unit-part-1-good-codechapter-2-readabilityitem201320avoid20re.md: -------------------------------------------------------------------------------- 1 | # \[第13条:避免返回或操作 Unit? ]\(Part 1 Good code/Chapter 2 Readability/Item%2013%20Avoid%20returning%20or%20operating%20on%20Unit%3F.md) 2 | 3 | -------------------------------------------------------------------------------- /part-1-good-code/chapter-2-readability/di-14-tiao-zai-lei-xing-bu-ming-que-de-qing-kuang-xia-qing-xian-shi-zhi-ding-bian-liang-de-lei-xing.md: -------------------------------------------------------------------------------- 1 | # \[第14条:在类型不明确的情况下,请显式指定变量的类型]\(Part 1 Good code/Chapter 2 Readability/Item 14 Specify the variable type when it is not clear.md) 2 | 3 | -------------------------------------------------------------------------------- /part-1-good-code/chapter-2-readability/di-15-tiao-kao-lv-ming-que-zhi-ding-jie-shou-zhe-part-1-good-codechapter-2-readabilityitem-15-consid.md: -------------------------------------------------------------------------------- 1 | # \[第15条:考虑明确指定接收者]\(Part 1 Good code/Chapter 2 Readability/Item 15 Consider referencing receivers explicitly.md) 2 | 3 | -------------------------------------------------------------------------------- /part-1-good-code/chapter-2-readability/di-16-tiao-shu-xing-ying-dai-biao-zhuang-tai-er-bu-shi-hang-wei-part-1-good-codechapter-2-readabilit.md: -------------------------------------------------------------------------------- 1 | # \[第16条:属性应代表状态,而不是行为]\(Part 1 Good code/Chapter 2 Readability/Item 16 Properties should represent state not behavior.md) 2 | 3 | -------------------------------------------------------------------------------- /part-1-good-code/chapter-2-readability/di-17-tiao-kao-lv-ming-ming-can-shu-part-1-good-codechapter-2-readabilityitem-17-consider-naming-arg.md: -------------------------------------------------------------------------------- 1 | # \[第17条:考虑命名参数]\(Part 1 Good code/Chapter 2 Readability/Item 17 Consider naming arguments.md) 2 | 3 | -------------------------------------------------------------------------------- /part-1-good-code/chapter-2-readability/di-18-tiao-zun-zhong-bian-ma-gui-fan-part-1-good-codechapter-2-readabilityitem-18-respect-coding-con.md: -------------------------------------------------------------------------------- 1 | # \[第18条:尊重编码规范]\(Part 1 Good code/Chapter 2 Readability/Item 18 Respect coding conventions.md) 2 | 3 | -------------------------------------------------------------------------------- /part-1-good-code/chapter-2-readability/yin-yan-part-1-good-codechapter-2-readabilityintroduction.md.md: -------------------------------------------------------------------------------- 1 | # \[引言]\(Part 1 Good code/Chapter 2 Readability/Introduction.md) 2 | 3 | -------------------------------------------------------------------------------- /part-2-code-design/README.md: -------------------------------------------------------------------------------- 1 | # Part 2 Code Design 2 | 3 | -------------------------------------------------------------------------------- /part-2-code-design/chapter-3-reusability/README.md: -------------------------------------------------------------------------------- 1 | # Chapter 3 Reusability 2 | 3 | -------------------------------------------------------------------------------- /part-2-code-design/chapter-3-reusability/introduction-part-2-code-design-chapter-3-reusability-introduction.md.md: -------------------------------------------------------------------------------- 1 | # \[Introduction]\(Part 2 Code design/Chapter 3 Reusability/Introduction.md) 2 | 3 | -------------------------------------------------------------------------------- /part-2-code-design/chapter-3-reusability/item-19-do-not-repeat-knowledge-part-2-code-design-chapter-3-reusability-item-19-do-not-repeat-knowl.md: -------------------------------------------------------------------------------- 1 | # \[Item 19 Do Not Repeat Knowledge]\(Part 2 Code design/Chapter 3 Reusability/Item 19 Do not repeat knowledge.md) 2 | 3 | -------------------------------------------------------------------------------- /part-2-code-design/chapter-3-reusability/item-20-do-not-repeat-common-algorithms-part-2-code-design-chapter-3-reusability-item-20-do-not-repe.md: -------------------------------------------------------------------------------- 1 | # \[Item 20 Do Not Repeat Common Algorithms]\(Part 2 Code design/Chapter 3 Reusability/Item 20 Do not repeat common algorithms.md) 2 | 3 | -------------------------------------------------------------------------------- /part-2-code-design/chapter-3-reusability/item-21-use-property-delegation-to-extract-common-property-patterns-part-2-code-design-chapter-3-reu.md: -------------------------------------------------------------------------------- 1 | # \[Item 21 Use Property Delegation To Extract Common Property Patterns]\(Part 2 Code design/Chapter 3 Reusability/Item 21 Use property delegation to extract common property patterns.md) 2 | 3 | -------------------------------------------------------------------------------- /part-2-code-design/chapter-3-reusability/item-22-use-generics-when-implementing-common-algorithms-part-2-code-design-chapter-3-reusability-it.md: -------------------------------------------------------------------------------- 1 | # \[Item 22 Use Generics When Implementing Common Algorithms]\(Part 2 Code design/Chapter 3 Reusability/Item 22 Use generics when implementing common algorithms.md) 2 | 3 | -------------------------------------------------------------------------------- /part-2-code-design/chapter-3-reusability/item-23-avoid-shadowing-type-parameters-part-2-code-design-chapter-3-reusability-item-23-avoid-shado.md: -------------------------------------------------------------------------------- 1 | # \[Item 23 Avoid Shadowing Type Parameters]\(Part 2 Code design/Chapter 3 Reusability/Item 23 Avoid shadowing type parameters.md) 2 | 3 | -------------------------------------------------------------------------------- /part-2-code-design/chapter-3-reusability/item-24-consider-variance-for-generic-types-part-2-code-design-chapter-3-reusability-item-24-conside.md: -------------------------------------------------------------------------------- 1 | # \[Item 24 Consider Variance For Generic Types]\(Part 2 Code design/Chapter 3 Reusability/Item 24 Consider variance for generic types.md) 2 | 3 | -------------------------------------------------------------------------------- /part-2-code-design/chapter-3-reusability/item-25-reuse-between-different-platforms-by-extracting-common-modules-part-2-code-design-chapter-3.md: -------------------------------------------------------------------------------- 1 | # \[Item 25 Reuse Between Different Platforms By Extracting Common Modules]\(Part 2 Code design/Chapter 3 Reusability/Item 25 Reuse between different platforms by extracting common modules.md) 2 | 3 | -------------------------------------------------------------------------------- /part-2-code-design/chapter-4-abstraction-design/README.md: -------------------------------------------------------------------------------- 1 | # Chapter 4 Abstraction Design 2 | 3 | -------------------------------------------------------------------------------- /part-2-code-design/chapter-4-abstraction-design/introduction-part-2-code-design-chapter-4-abstraction-design-introduction.md.md: -------------------------------------------------------------------------------- 1 | # \[Introduction]\(Part 2 Code design/Chapter 4 Abstraction design/Introduction.md) 2 | 3 | -------------------------------------------------------------------------------- /part-2-code-design/chapter-4-abstraction-design/item-26-each-function-should-be-written-in-terms-of-a-single-level-of-abstraction-part-2-code-design.md: -------------------------------------------------------------------------------- 1 | # \[Item 26 Each Function Should Be Written In Terms Of A Single Level Of Abstraction]\(Part 2 Code design/Chapter 4 Abstraction design/Item 26 Each function should be written in terms of a single level of abstraction.md) 2 | 3 | -------------------------------------------------------------------------------- /part-2-code-design/chapter-4-abstraction-design/item-27-use-abstraction-to-protect-code-against-changes-part-2-code-design-chapter-4-abstraction-des.md: -------------------------------------------------------------------------------- 1 | # \[Item 27 Use Abstraction To Protect Code Against Changes]\(Part 2 Code design/Chapter 4 Abstraction design/Item 27 Use abstraction to protect code against changes.md) 2 | 3 | -------------------------------------------------------------------------------- /part-2-code-design/chapter-4-abstraction-design/item-28-specify-api-stability-part-2-code-design-chapter-4-abstraction-design-item-28-specify-api-st.md: -------------------------------------------------------------------------------- 1 | # \[Item 28 Specify API Stability]\(Part 2 Code design/Chapter 4 Abstraction design/Item 28 Specify API stability.md) 2 | 3 | -------------------------------------------------------------------------------- /part-2-code-design/chapter-4-abstraction-design/item-29-consider-wrapping-external-api-part-2-code-design-chapter-4-abstraction-design-item-29-consi.md: -------------------------------------------------------------------------------- 1 | # \[Item 29 Consider Wrapping External API]\(Part 2 Code design/Chapter 4 Abstraction design/Item 29 Consider wrapping external API.md) 2 | 3 | -------------------------------------------------------------------------------- /part-2-code-design/chapter-4-abstraction-design/item-30-minimize-elements-visibility-part-2-code-design-chapter-4-abstraction-design-item-30-minimiz.md: -------------------------------------------------------------------------------- 1 | # \[Item 30 Minimize Elements Visibility]\(Part 2 Code design/Chapter 4 Abstraction design/Item 30 Minimize elements visibility.md) 2 | 3 | -------------------------------------------------------------------------------- /part-2-code-design/chapter-4-abstraction-design/item-31-define-contract-with-documentation-part-2-code-design-chapter-4-abstraction-design-item-31-d.md: -------------------------------------------------------------------------------- 1 | # \[Item 31 Define Contract With Documentation]\(Part 2 Code design/Chapter 4 Abstraction design/Item 31 Define contract with documentation.md) 2 | 3 | -------------------------------------------------------------------------------- /part-2-code-design/chapter-4-abstraction-design/item-32-respect-abstraction-contracts-part-2-code-design-chapter-4-abstraction-design-item-32-respec.md: -------------------------------------------------------------------------------- 1 | # \[Item 32 Respect Abstraction Contracts]\(Part 2 Code design/Chapter 4 Abstraction design/Item 32 Respect abstraction contracts.md) 2 | 3 | -------------------------------------------------------------------------------- /part-2-code-design/chapter-5-object-creation/README.md: -------------------------------------------------------------------------------- 1 | # Chapter 5 Object Creation 2 | 3 | -------------------------------------------------------------------------------- /part-2-code-design/chapter-5-object-creation/introduction-part-2-code-design-chapter-5-object-creation-introduction.md.md: -------------------------------------------------------------------------------- 1 | # \[Introduction]\(Part 2 Code design/Chapter 5 Object creation/Introduction.md) 2 | 3 | -------------------------------------------------------------------------------- /part-2-code-design/chapter-5-object-creation/item-33-consider-factory-functions-instead-of-constructors-part-2-code-design-chapter-5-object-creat.md: -------------------------------------------------------------------------------- 1 | # \[Item 33 Consider Factory Functions Instead Of Constructors]\(Part 2 Code design/Chapter 5 Object creation/Item 33 Consider factory functions instead of constructors.md) 2 | 3 | -------------------------------------------------------------------------------- /part-2-code-design/chapter-5-object-creation/item-34-consider-a-primary-constructor-with-named-optional-arguments-part-2-code-design-chapter-5-ob.md: -------------------------------------------------------------------------------- 1 | # \[Item 34 Consider A Primary Constructor With Named Optional Arguments]\(Part 2 Code design/Chapter 5 Object creation/Item 34 Consider a primary constructor with named optional arguments.md) 2 | 3 | -------------------------------------------------------------------------------- /part-2-code-design/chapter-5-object-creation/item-35-consider-defining-a-dsl-for-complex-object-creation-part-2-code-design-chapter-5-object-crea.md: -------------------------------------------------------------------------------- 1 | # \[Item 35 Consider Defining A DSL For Complex Object Creation]\(Part 2 Code design/Chapter 5 Object creation/Item 35 Consider defining a DSL for complex object creation.md) 2 | 3 | -------------------------------------------------------------------------------- /part-2-code-design/chapter-6-class-design/README.md: -------------------------------------------------------------------------------- 1 | # Chapter 6 Class Design 2 | 3 | -------------------------------------------------------------------------------- /part-2-code-design/chapter-6-class-design/introduction-part-2-code-design-chapter-6-class-design-introduction.md.md: -------------------------------------------------------------------------------- 1 | # \[Introduction]\(Part 2 Code design/Chapter 6 Class design/Introduction.md) 2 | 3 | -------------------------------------------------------------------------------- /part-2-code-design/chapter-6-class-design/item-36-prefer-composition-over-inheritance-part-2-code-design-chapter-6-class-design-item-36-prefer.md: -------------------------------------------------------------------------------- 1 | # \[Item 36 Prefer Composition Over Inheritance]\(Part 2 Code design/Chapter 6 Class design/Item 36 Prefer composition over inheritance.md) 2 | 3 | -------------------------------------------------------------------------------- /part-2-code-design/chapter-6-class-design/item-37-use-the-data-modifier-to-represent-a-bundle-of-data-part-2-code-design-chapter-6-class-desig.md: -------------------------------------------------------------------------------- 1 | # \[Item 37 Use The Data Modifier To Represent A Bundle Of Data]\(Part 2 Code design/Chapter 6 Class design/Item 37 Use the data modifier to represent a bundle of data.md) 2 | 3 | -------------------------------------------------------------------------------- /part-2-code-design/chapter-6-class-design/item-38-use-function-types-instead-of-interfaces-to-pass-operations-and-actions-part-2-code-design-c.md: -------------------------------------------------------------------------------- 1 | # \[Item 38 Use Function Types Instead Of Interfaces To Pass Operations And Actions]\(Part 2 Code design/Chapter 6 Class design/Item 38 Use function types instead of interfaces to pass operations and actions.md) 2 | 3 | -------------------------------------------------------------------------------- /part-2-code-design/chapter-6-class-design/item-39-prefer-class-hierarchies-to-tagged-classes-part-2-code-design-chapter-6-class-design-item-39.md: -------------------------------------------------------------------------------- 1 | # \[Item 39 Prefer Class Hierarchies To Tagged Classes]\(Part 2 Code design/Chapter 6 Class design/Item 39 Prefer class hierarchies to tagged classes.md) 2 | 3 | -------------------------------------------------------------------------------- /part-2-code-design/chapter-6-class-design/item-40-respect-the-contract-of-equals-part-2-code-design-chapter-6-class-design-item-40-respect-the.md: -------------------------------------------------------------------------------- 1 | # \[Item 40 Respect The Contract Of Equals]\(Part 2 Code design/Chapter 6 Class design/Item 40 Respect the contract of equals.md) 2 | 3 | -------------------------------------------------------------------------------- /part-2-code-design/chapter-6-class-design/item-41-respect-the-contract-of-hash-code-part-2-code-design-chapter-6-class-design-item-41-respect.md: -------------------------------------------------------------------------------- 1 | # \[Item 41 Respect The Contract Of Hash Code]\(Part 2 Code design/Chapter 6 Class design/Item 41 Respect the contract of hashCode.md) 2 | 3 | -------------------------------------------------------------------------------- /part-2-code-design/chapter-6-class-design/item-42-respect-the-contract-of-compare-to-part-2-code-design-chapter-6-class-design-item-42-respect.md: -------------------------------------------------------------------------------- 1 | # \[Item 42 Respect The Contract Of Compare To]\(Part 2 Code design/Chapter 6 Class design/Item 42 Respect the contract of compareTo.md) 2 | 3 | -------------------------------------------------------------------------------- /part-2-code-design/chapter-6-class-design/item-43-consider-extracting-non-essential-parts-of-your-api-into-extensions-part-2-code-design-chapt.md: -------------------------------------------------------------------------------- 1 | # \[Item 43 Consider Extracting Non Essential Parts Of Your API Into Extensions]\(Part 2 Code design/Chapter 6 Class design/Item 43 Consider extracting non-essential parts of your API into extensions.md) 2 | 3 | -------------------------------------------------------------------------------- /part-2-code-design/chapter-6-class-design/item-44-avoid-member-extensions-part-2-code-design-chapter-6-class-design-item-44-avoid-member-exten.md: -------------------------------------------------------------------------------- 1 | # \[Item 44 Avoid Member Extensions]\(Part 2 Code design/Chapter 6 Class design/Item 44 Avoid member extensions.md) 2 | 3 | -------------------------------------------------------------------------------- /part-3-efficiency/README.md: -------------------------------------------------------------------------------- 1 | # Part 3 Efficiency 2 | 3 | -------------------------------------------------------------------------------- /part-3-efficiency/chapter-7-make-it-cheap/README.md: -------------------------------------------------------------------------------- 1 | # Chapter 7 Make It Cheap 2 | 3 | -------------------------------------------------------------------------------- /part-3-efficiency/chapter-7-make-it-cheap/introduction-part-3-efficiency-chapter-7-make-it-cheap-introduction.md.md: -------------------------------------------------------------------------------- 1 | # \[Introduction]\(Part 3 Efficiency/Chapter 7 Make it cheap/Introduction.md) 2 | 3 | -------------------------------------------------------------------------------- /part-3-efficiency/chapter-7-make-it-cheap/item-45-avoid-unnecessary-object-creation-part-3-efficiency-chapter-7-make-it-cheap-item-45-avoid-un.md: -------------------------------------------------------------------------------- 1 | # \[Item 45 Avoid Unnecessary Object Creation]\(Part 3 Efficiency/Chapter 7 Make it cheap/Item 45 Avoid unnecessary object creation.md) 2 | 3 | -------------------------------------------------------------------------------- /part-3-efficiency/chapter-7-make-it-cheap/item-46-use-inline-modifier-for-functions-with-parameters-of-functional-types-part-3-efficiency-chap.md: -------------------------------------------------------------------------------- 1 | # \[Item 46 Use Inline Modifier For Functions With Parameters Of Functional Types]\(Part 3 Efficiency/Chapter 7 Make it cheap/Item 46 Use inline modifier for functions with parameters of functional types.md) 2 | 3 | -------------------------------------------------------------------------------- /part-3-efficiency/chapter-7-make-it-cheap/item-47-consider-using-inline-classes-part-3-efficiency-chapter-7-make-it-cheap-item-47-consider-usi.md: -------------------------------------------------------------------------------- 1 | # \[Item 47 Consider Using Inline Classes]\(Part 3 Efficiency/Chapter 7 Make it cheap/Item 47 Consider using inline classes.md) 2 | 3 | -------------------------------------------------------------------------------- /part-3-efficiency/chapter-7-make-it-cheap/item-48-eliminate-obsolete-object-references-part-3-efficiency-chapter-7-make-it-cheap-item-48-elimi.md: -------------------------------------------------------------------------------- 1 | # \[Item 48 Eliminate Obsolete Object References]\(Part 3 Efficiency/Chapter 7 Make it cheap/Item 48 Eliminate obsolete object references.md) 2 | 3 | -------------------------------------------------------------------------------- /part-3-efficiency/chapter-8-efficient-collection-processing/README.md: -------------------------------------------------------------------------------- 1 | # Chapter 8 Efficient Collection Processing 2 | 3 | -------------------------------------------------------------------------------- /part-3-efficiency/chapter-8-efficient-collection-processing/di-50-tiao-jian-shao-cao-zuo-de-ci-shu-part-3-efficiencychapter-8-efficient-collection-processingite.md: -------------------------------------------------------------------------------- 1 | # \[第50条:减少操作的次数]\(Part 3 Efficiency/Chapter 8 Efficient collection processing/Item 50 Limit the number of operations.md) 2 | 3 | -------------------------------------------------------------------------------- /part-3-efficiency/chapter-8-efficient-collection-processing/di-51-tiao-zai-xing-neng-you-xian-de-chang-jing-kao-lv-shi-yong-ji-chu-lei-xing-shu-zu-part-3-effici.md: -------------------------------------------------------------------------------- 1 | # \[第51条:在“性能优先”的场景,考虑使用基础类型数组]\(Part 3 Efficiency/Chapter 8 Efficient collection processing/Item 51 Consider Arrays with primitives for performance-critical processing.md) 2 | 3 | -------------------------------------------------------------------------------- /part-3-efficiency/chapter-8-efficient-collection-processing/di-52-tiao-zai-chu-li-ju-bu-bian-liang-shi-kao-lv-shi-yong-ke-bian-ji-he-part-3-efficiencychapter-8.md: -------------------------------------------------------------------------------- 1 | # \[第52条:在处理局部变量时,考虑使用可变集合]\(Part 3 Efficiency/Chapter 8 Efficient collection processing/Item 52 Consider using mutable collections.md) 2 | 3 | -------------------------------------------------------------------------------- /part-3-efficiency/chapter-8-efficient-collection-processing/introduction-part-3-efficiency-chapter-8-efficient-collection-processing-introduction.md.md: -------------------------------------------------------------------------------- 1 | # \[Introduction]\(Part 3 Efficiency/Chapter 8 Efficient collection processing/Introduction.md) 2 | 3 | -------------------------------------------------------------------------------- /part-3-efficiency/chapter-8-efficient-collection-processing/item-49-prefer-sequence-for-big-collections-with-more-than-one-processing-step-part-3-efficiency-cha.md: -------------------------------------------------------------------------------- 1 | # \[Item 49 Prefer Sequence For Big Collections With More Than One Processing Step]\(Part 3 Efficiency/Chapter 8 Efficient collection processing/Item 49 Prefer Sequence for big collections with more than one processing step.md) 2 | 3 | --------------------------------------------------------------------------------