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

可読性を高めるためのコードレビューテクニック

 可読性を高めるためのコードレビューテクニック

2023年3月16日に開催された「SoftBank Tech Night Fes 2023」の講演資料です。

コードレビューを行うことで、ロジックのバグやセキュリティ上の欠陥、パフォーマンスの問題といった、間違いを発見することができます。その「間違いを発見する」効果が注目されがちですが、コードレビューは「コードの可読性を高める」ためにも重要です。第三者の視点でコードを見ることで、書いた人だけが持つ前提知識がなくても読めるコードになっているかを確認できます。このセッションでは、コードレビューを通じてどのように可読性を高めるのか、そのテクニックを抜粋して紹介します。

■関連リンク
・LINE Engineering
https://engineering.linecorp.com/ja/

■作成者
石川 宗寿(いしかわ むねとし)
LINE株式会社
LINE Platform Developmentセンター2 モバイルエクスペリエンス開発室
ディベロッパーエクスペリエンス開発チーム
シニアソフトウェアエンジニア

■SoftBank Tech Nightについて
ソフトバンク株式会社のエンジニア有志が開催するテックイベントです。
各分野のエキスパートが、日頃培った技術や事例、知見について発信しています。
イベントは開催スケジュールはconnpassをご確認ください。
https://sbtechnight.connpass.com/

SoftBank Tech Night Fes 2023公式サイト
https://www.softbank.jp/biz/events/tech-night-fes-2023/

SoftBank Tech Night

March 16, 2023
Tweet

More Decks by SoftBank Tech Night

Other Decks in Technology

Transcript

  1. Objective of code review Detects points that may be overlooked

    by the author - Find problems: bug, security or performance issues - Improve readability
  2. Assumptions for this talk - Developing a product in a

    team - Using GitHub for codebase hosting merge request review pull request author reviewer “Twemoji” ©Twitter, Inc and other contributors (Licensed under CC-BY 4.0) https://twemoji.twitter.com/ codebase
  3. Contents of this talk - Check out a pull-request -

    Scan the code - Emulate another code reviewer - Use other communication tools in addition to review comments - Don't review problematic pull-requests
  4. Contents of this talk - Check out a pull-request -

    Scan the code - Emulate another code reviewer - Use other communication tools in addition to review comments - Don't review problematic pull-requests
  5. Check out a pull-request May overlook issues only by looking

    at the changed code private fun storeMessages(messages: List<MessageModel>) { if (messages.isEmpty) { return } ... } fun existingCaller(messages: List<MessageModel>) { if (messages.isNotEmpty) { storeMessages(messages) } } newly added code duplicated conditional branch
  6. What to confirm by checking out - Inconsistent name -

    Code duplication - Replaceable code with standard library/API - Layer to implement logic: caller or callee - Warnings and metrics
  7. Example: Inconsistent name May need to rename existing properties when

    a new one is added class FooEntry( val !!", val timestampInMillis: Long, !!" )
  8. May need to rename existing properties when a new one

    is added Example: Inconsistent name class FooEntry( val !!", val timestampInMillis: Long, !!" val lastUpdateTimestampInMillis: Long ) newly added property
  9. May need to rename existing properties when a new one

    is added Example: Inconsistent name class FooEntry( val !!", !!" val creationTimestampInMillis: Long, val lastUpdateTimestampInMillis: Long ) newly added property need to rename & reorder
  10. Tools to check out a pull-request To check out pull-request

    #123456
 - GitHub CLI: https://cli.github.com gh pr checkout 123456 - `hub` command: https://github.com/github/hub hub checkout https://github.com/.../pull/123456
  11. Check out a pull-request: Summary Don't focus only on the

    changed code - Check existing code as well - Use a tool to check out a pull request
  12. Contents of this talk - Check out a pull-request -

    Scan the code - Emulate another code reviewer - Use other communication tools in addition to review comments - Don't review problematic pull-requests
  13. Scan the code Code should be able to be outlined

    without reading the detail Able to understand with: - Signature (name, type, parameters, etc.) - Documentation comment - Happy path logic (= the main case logic) - The left-side of a code block
  14. Example 1/2: Conditional branch, bad case Need to read if

    body to understand the function responsibility fun ...(inputNumber: Int) { if (inputNumber < 0) { submitButton.isEnabled = false messageTextView.text = "Invalid number!" } else { submitButton.isEnabled = true messageTextView.text = "Valid number." } }
  15. Example 1/2: Conditional branch, bad case Need to read if

    body to understand the function responsibility fun ...(inputNumber: Int) { if (inputNumber < 0) { ... ... } else { ... ... } } Code summary:
 Do something depending on whether inputNumber is valid or not
  16. Example 1/2: Conditional branch, good case Can understand what the

    function does without reading the details fun someFunction(inputNumber: Int) { val isValidNumber = inputNumber >= 0 submitButton.isEnabled = isValidNumber messageTextView.text = if (isValidNumber) "Invalid number!" else "Valid number." }
  17. Example 1/2: Conditional branch, good case Can understand what the

    function does without reading the details fun someFunction(inputNumber: Int) { val isValidNumber = ... submitButton.isEnabled = ... messageTextView.text = ... } Code summary: Enable/disable the button and update the text
 depending on whether inputNumber is valid or not
  18. Example 2/2: Method chain, bad case Need to read all

    the lambda bodies and the receivers return userActionLog .groupBy { it.user } .map { it.key to it.value.size } .sortedBy { it.second } .map { it.first } .takeLast(10)
  19. Example 2/2: Method chain, bad case Need to read all

    the lambda bodies and the receivers return userActionLog .groupBy ... .map ... .sortedBy ... .map ... .takeLast(10) Code summary: Return a sub-list created from userActionLog
  20. Example 1/2: Method chain, good case Can understand the return

    value without reading the chain val logCountByUser: Map<User, Int> = userActionLog .groupBy { log -> log.user } .map { (user, logs) -> user to logs.size } val userListSortedByLogCount: List<User> = logCountByUser .sortedBy { (_, messageCount) -> messageCount } .map { (userId, _) -> userId } return userListSortedByLogCount.takeLast(10)
  21. Example 1/2: Method chain, good case Can understand the return

    value without reading the chain val logCountByUser: Map<User, Int> = userActionLog ... ... val userListSortedByLogCount: List<User> = logCountByUser ... ... return userListSortedByLogCount.takeLast(10) Code summary: Return the 10 users with the highest log count
  22. Why we scan at code review Readable for author !=

    readable for other members 
 Knowledge gap between authors and readers/reviewers - Authors: write code with knowledge of the details - Readers/reviewers: read the code without the knowledge
  23. How to confirm if the code is scannable "Got it!"

    is a bad signal - "Why is null check needed here?" - "Got it! It means a non-cached value." val messageModel = messageCache[messageKey] ... if (messageModel == null || ...) { ... }
  24. How to make the code scannable - Renaming, writing comments

    - Restructuring val cachedMessageModel = messageCache[messageKey] ... if (cachedMessageModel == null || ...) { ... }
  25. Scan the code: Summary Check the code is readable without

    understanding the details - "Got it!" is a bad signal
  26. Contents of this talk - Check out a pull-request -

    Scan the code - Emulate another code reviewer - Use other communication tools in addition to review comments - Don't review problematic pull-requests
  27. Emulate another code reviewer Check if the code is readable

    for all team members - You may have feature specific knowledge - Avoid knowledge siloing Imagine whether another reviewer can understand or not
  28. Image of emulation 1/2: Normal code reading reviewer module specific

    knowledge computer science knowledge platform/language knowledge context of the project “Twemoji” ©Twitter, Inc and other contributors (Licensed under CC-BY 4.0) https://twemoji.twitter.com/ fun function(foo: Int) { if (foo == 42) { return } ... } "Why 42?" "Got it!" guessing, deduction...
  29. Assume prerequisite knowledge Can't expect feature/module/project specific knowledge or context


    
 Things that new team members should know: - Basic computer science - Basic knowledge of the programming language and platform - Knowledge of tools used in the organization (if training is available)
  30. Image of emulation 2/2: With emulation reviewer imaginary new member

    computer science knowledge platform/language knowledge “Twemoji” ©Twitter, Inc and other contributors (Licensed under CC-BY 4.0) https://twemoji.twitter.com/ fun function(foo: Int) { if (foo == 42) { return } ... } "Why 42?" ???
  31. Emulate another code reviewer: Summary Check if the code is

    readable for all team members - Assume prerequisite knowledge - Imagine how a new member understand the code
  32. Contents of this talk - Check out a pull-request -

    Scan the code - Emulate another code reviewer - Use other communication tools in addition to review comments - Don't review problematic pull-requests
  33. Use other communication tools Communication on review comments can be

    slow - 6pm, Mar 6. An author send a review request - 1pm, Mar 7. Reviewer: "Why do you check null here?" - 5pm, Mar 7. Author: "This is because..." - 2pm, Mar 8. Reviewer: "If so, we can restructure..." Consider using another communication way
  34. Considerable options Face-to-face discussion - With a white board, bringing

    a computer, etc.
 Audio/video conference - With screen sharing, pair programming tools, etc.
 Messaging tools
  35. Follow up of communication Leave a comment to explain the

    conclusion - To help other members understand the conclusions - To be able to trace the context later
  36. Useful phrases for leaving a note - "For the record:

    ..." - "As we chatted offline", "As we discussed on another thread" - "Refer to (chat log link) for more details"
  37. Use other communication tools: Summary Chat offline/online or using messaging

    tool - Leave a comment on the pull-request to explain the conclusion
  38. Contents of this talk - Check out a pull-request -

    Scan the code - Emulate another code reviewer - Use other communication tools in addition to review comments - Don't review problematic pull-requests
  39. Don't review problematic pull-requests Don't spend too much time on

    a problematic pull-request - Too large - Has too many issues - Has a fundamental issue on the objective/structure
  40. Drawbacks of reviewing problematic pull requests - May overlook issues

    - Consumes too much reviewer's and author's resources
  41. Actions for problematic pull requests 1. Ask to close the

    pull request first
 
 
 
 
 
 2. Follow up for the next step
  42. Options of the next steps - Split the pull-request into

    smaller ones - Write a document for design (design-doc) - Make skeleton classes first
  43. Make skeleton classes first Focus only on the structure first

    class UserProfilePresenter( val useCase: UserProfileUseCase, val profileRootView: View ) { fun showProfileImage(): UiResult { ... // Implementation detail } private fun someDetailedPrivateFunction() { ... // Implementation detail } }
  44. Make skeleton classes first Focus only on the structure first

    Review the detail code after the structure class UserProfilePresenter( val useCase: UserProfileUseCase, val profileRootView: View ) { fun showProfileImage(): UiResult = TODO("!!"") }
  45. Don't review problematic pull-requests Ask to close, and follow up

    - Save reviewer's and author's resources - Follow up is mandatory
  46. Summary - Check out a pull-request - Scan the code

    - Emulate another code reviewer - Use other communication tools in addition to review comments - Don't review problematic pull-requests
  47. Summary - Read existing code as well - Check the

    code is readable without understanding the details - Confirm if the code is readable for new members - Chat offline/online or using messaging tool - Ask to close, and follow up
  48. Related materials Watch a presentation from DroidKaigi 2021
 
 ௕͘ੜ͖Δίʔυϕʔεͷʮ඼࣭ʯ໰୊ʹ޲͖߹͏


    https://droidkaigi.jp/2021/timetable/276957/?day=2 1. Process and activities for readability 2. Code readability