├── style ├── react.md ├── java.md ├── graphql.md ├── css.md ├── javascript.md ├── ios.md ├── python.md ├── android-development-best-practices.md └── go.md ├── Readme.md ├── gh-md-toc └── configs └── KhanAcademyAndroid.xml /style/react.md: -------------------------------------------------------------------------------- 1 | # React style guide 2 | 3 | ---- 4 | 5 | > Follow the normal [JavaScript style guide](javascript.md). We generally try to follow the official [React tutorials and guides](https://react.dev/reference/react). Please default to them as a source-of-truth. 6 | 7 | ---- 8 | 9 | ## Syntax 10 | 11 | ### Prefer to export a single React component 12 | 13 | Every .tsx file should ideally export a single React component. 14 | This is for testability and composition; it's easier to write Storybook stories and 15 | it's clearer where the breaks in functionality are. 16 | 17 | Note that the file can still define multiple components, it just shouldn't export 18 | more than one. 19 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # Khan Academy Coding Style Guides 2 | 3 | We implement a style guide for our code with the intention of keeping things readable and consistent. Please do your part on the team to help keep the spirit of this consistency in both your own code, as well as politely pointing out violations in other people's code when doing their code reviews. While code prettiness should never be valued over launching or any user-visible impacting changes to the code, the idea is that maintaining a readable codebase helps things be more maintainable, and in the long run will make it easier to do the real changes that do make user-visible changes. 4 | 5 | There may be lots of legacy files that do not adhere to the current style guide; if you're editing an old file, be consistent with what's around you. 6 | 7 | To help adhere to these rules, some tools are available to automatically catch, and in some cases fix, style violations. See the per-language guides below for more info. 8 | 9 | ## TODOs 10 | 11 | If there is something that you want to deal with later, it’s appropriate to mark it in code. There’s an advantage to using a standard format, which is this: 12 | 13 | ``` 14 | # TODO(your_username): Fix this to work with frobnozzes too 15 | # TODO(your_username): Remove this once we support quxxes (at least by Dec 2012) 16 | ``` 17 | 18 | The text TODO is followed by your username in parentheses. This does not mean that you are on the hook to follow through on the TODO. Rather, it means that you are the person most knowledgeable about it, so if others run across the TODO and have questions about it, they know who to talk to. 19 | 20 | In code reviews, it is common to put in TODOs when a reviewer points out some thing in the code that could be improved, but is not necessary to do right away. 21 | 22 | ## Language Style Guides 23 | 24 | - [JavaScript](/style/javascript.md) 25 | - [React](/style/react.md) 26 | - [CSS](/style/css.md) 27 | - [Python](/style/python.md) 28 | - [Go](/style/go.md) 29 | - [Java](/style/java.md) 30 | - [ObjC](https://github.com/Khan/objective-c-style-guide) (hosted separately) 31 | - [iOS](/style/ios.md) 32 | -------------------------------------------------------------------------------- /style/java.md: -------------------------------------------------------------------------------- 1 | # Java 2 | 3 | At Khan Academy, we only use Java for Android and, as such, follow the [Android Code Style Guidelines for Contributors](https://source.android.com/source/code-style.html). Note that those rules themselves extend the [standard Java language style guide as of 1999](http://www.oracle.com/technetwork/java/javase/documentation/codeconventions-141855.html). The rest of this document describes additions and clarifications to those rules that we follow at Khan Academy. 4 | 5 | To import these settings for Android Studio, copy [the config file](/configs/KhanAcademyAndroid.xml) into `$HOME/Library/Preferences/AndroidStudio/codestyles/`. 6 | 7 | ## Imports 8 | 9 | Import statements are divided into the following groups, in this order, with each group separated by a blank line: 10 | 11 | 1. All static imports in a single group 12 | 2. org.khanacademy imports 13 | 3. All other non-Android third-party imports, in ASCII sort order (for example: com, junit, org, sun) 14 | 4. android imports 15 | 5. java imports 16 | 6. javax imports 17 | 18 | Imports are not subject to the 100 column limit rule; they should never be wrapped. 19 | 20 | We prefer using static imports when using the following: 21 | 22 | - `com.google.common.base.Preconditions.*` (e.g. `checkNotNull`) 23 | - `org.junit.Assert.*` (e.g. `assertEquals`) 24 | 25 | 26 | ## No per-file boilerplate 27 | 28 | We do not include copyright or `@author` or `Created by` boilerplate at the top of files. The first line of the file is typically the `package` statement. 29 | 30 | Package statements are not subject to the 100 column limit rule; they should never be wrapped. 31 | 32 | 33 | ## Use your best judgement for Javadoc 34 | 35 | We don't strictly require Javadoc for _all_ public methods, though it's _strongly_ encouraged for non-trivial ones. 36 | 37 | 38 | ## Use `@Override` where possible 39 | 40 | In addition to requiring the `@Override` tag when a class overrides the declration or implementation from a superclass, we also require it to be used when classes implement a method from an implemented interface. 41 | 42 | 43 | ## No single-line if statements. 44 | 45 | Bad: 46 | ```java 47 | 48 | if (foo) {bar();} // No single-line if statements 49 | 50 | // ...and braces are always required 51 | if (foo) 52 | bar(); 53 | ``` 54 | 55 | Good: 56 | ```java 57 | if (foo) { 58 | bar(); 59 | } 60 | ``` 61 | 62 | 63 | ## No C-style array declarations 64 | 65 | The square brackets form part of the _type_, not the variable. 66 | 67 | Good: `String[] args` 68 | Bad: `String args[]` 69 | 70 | 71 | ## Use TODO comments with author name 72 | 73 | Extending [AOSP style guide](https://source.android.com/setup/code-style#use-todo-comments), TODOs should include the string TODO in all caps, the author's name in parentheses, and a colon: 74 | 75 | `// TODO(author): ...` 76 | 77 | 78 | ## Use `final` variables when possible 79 | 80 | Mark variables that should only be initialized once as `final`. This includes function arguments. 81 | 82 | -------------------------------------------------------------------------------- /style/graphql.md: -------------------------------------------------------------------------------- 1 | # Graphql 2 | 3 | ## Docstrings 4 | 5 | When writing a schema definition file (`graphql.schema`), every type 6 | and field should be documented. The schema file is the main source of 7 | documentation for users who are crafting graphql queries and need to 8 | know what fields to ask for. We use triple-quotes for documentation, 9 | since they are automatically picked up by GraphiQL and other graphql 10 | UIs. 11 | 12 | ### Example 13 | 14 | ```graphql 15 | """ 16 | Summary of what this type is for; must fit on one line. 17 | 18 | After the blank line, you can spend as much space talking 19 | about this type as you'd like. 20 | """ 21 | interface Book { 22 | """Summary of this field; must fit on one line. 23 | If you need more space for discussing this field, 24 | you can use more lines, but don't use a blank line 25 | here. If applicable, give examples of valid values. 26 | Examples: Sal Khan, Artemis Fowl 27 | """ 28 | author: String! 29 | 30 | # Normal (`#`-prefixed) comments are not exposed via GraphiQL. 31 | # They are appropriate for text that is useful for code-authors but 32 | # not code-users. 33 | """This field needs only one line to describe it.""" 34 | publishYear: Number 35 | 36 | # We don't need a docstring here; the documentation is inside @deprecated. 37 | published: Boolean @deprecated( 38 | reason: "Use publishYear instead.") 39 | 40 | """The company that published the book. 41 | Valid values: Random House, Macmillan, University of California Press 42 | """" 43 | publisher: String 44 | } 45 | 46 | type Hardcover implements Book { 47 | # These fields are defined on the Book interface. 48 | author: String! 49 | publishYear: Number 50 | published: Boolean 51 | 52 | """The number of pages the hardcover version of this book has.""" 53 | numPages: Number! 54 | } 55 | ``` 56 | 57 | ### Commenting types 58 | 59 | Top level datatypes -- `Type`, `Interface`, `Union`, etc -- have a 60 | line of `"""` by itself, followed by a one-line summary of the 61 | datatype, followed by a blank line, followed by other text describing 62 | the type, ending with a `"""` on its own line. 63 | 64 | This comment style helps point out, visually, where one type ends and 65 | a new type begins. 66 | 67 | ### Commenting fields 68 | 69 | Almost all fields within a `Type` should have a docstring. This 70 | docstring should start with a `"""` followed by a one-line summary of 71 | the field, all on one line. If more documentation is needed, text can 72 | follow on the next line. There should never be a blank line in a 73 | field-docstring. 74 | 75 | Unless it's entirely obvious, give an example of possible value or two 76 | via the line: `Example: ..., ....`. Do not put quotes around the 77 | examples unless it's needed for clarity. 78 | 79 | When appropriate -- especially when an object has type "String" but is 80 | semantically an enum -- include a line `Valid values: ..., ...` that 81 | lists all valid values that the field could have. 82 | 83 | If the docstring is one line long, the trailing `"""` should go on 84 | that one line. Otherwise, the trailing `"""` should go on a line of 85 | its own. 86 | 87 | There should always be a blank line between a field and the docstring 88 | that introduces the next field. 89 | 90 | Always use triple-quotes, never a single quote, even for one-line 91 | docstrings. 92 | 93 | This comment style makes it easy to find field definitions within a 94 | type. In particular, blank lines are *only* used to separate fields. 95 | There are never blank lines within the text pertaining to a field. 96 | 97 | ### Commenting deprecated fields 98 | 99 | If *new code* should not query on a particular field, mark that fact 100 | with a `@deprecated` tag. The `reason` associated with that tag 101 | should say what to use instead. A docstring is not needed for this 102 | field, since no new code should be using it. (It's not forbidden to 103 | have a docstring, though.) 104 | 105 | You can mark a field `@deprecated` even if existing graphql queries 106 | use it. The `@deprecated` tag is meant only as a signal for people 107 | writing *new* graphql queries, that they should not be using this 108 | field anymore. 109 | 110 | ### Commenting interfaces 111 | 112 | If you write an interface, you should document all the fields of that 113 | interface, just like any other type. 114 | 115 | When you define a type that implements that interface, you will need 116 | to copy the fields over again, but you do *not* need to copy the 117 | docstrings for those fields. 118 | 119 | In an ideal world, Graphiql and other graphql browsers would know to 120 | copy the docstrings from the interface to its various 121 | implementations. Right now they don't, but we'd rather fix them in 122 | code than make people copy documentation all over the place. 123 | -------------------------------------------------------------------------------- /style/css.md: -------------------------------------------------------------------------------- 1 | # CSS 2 | 3 | Welcome to the CSS style guide. This guide borrows heavily from Github's [Primer CSS guidelines](http://primercss.io/guidelines/), so thanks to them! 4 | 5 | ## Coding Style 6 | 7 | - Use a four space indent. 8 | - Put spaces after : in property declarations. 9 | - Spaces before braces: put spaces before { in rule declarations. 10 | - Do not use trailing commas when listing multiple selectors 11 | - Separate selector and property declarations with a new line 12 | - Separate rules with a new line 13 | - End properties declarations with a ; 14 | - Alphabetize properties within rule declarations 15 | - Properties with values of 0 should *not* have units 16 | - Use lowercase hex color codes #fff unless using rgba. 17 | - Use /* */ for block comments (instead of //). 18 | - Use a maximum line length of 80 characters (rationale: looking at files side-by-side) 19 | - Use dashes in selectors instead of underscores (.my-class, not .my_class) 20 | 21 | Here is good example syntax: 22 | 23 | ```css 24 | /* This is a good example! */ 25 | .styleguide-format, 26 | .other-format, 27 | .third-format { 28 | background: rgba(0,0,0,0.5); 29 | border: 1px solid #0f0; 30 | color: #000; 31 | margin: 0; 32 | } 33 | 34 | .next-rule { 35 | color: #000; 36 | } 37 | ``` 38 | 39 | ## CSS Specificity Guidelines 40 | 41 | Elements that occur **exactly once** inside a page should use IDs, otherwise, use classes. When in doubt, use a class name. 42 | 43 | - **Good** candidates for ids: header, footer, modal popups. 44 | - **Bad** candidates for ids: navigation, item listings, item view pages (ex: issue view). 45 | 46 | - If you must use an id selector (`#selector`) make sure that you have no more than one in your rule declaration. A rule like `#header .search #quicksearch { ... }` is considered harmful. Curious why? Check out this primer on [CSS specificity](http://css-tricks.com/specifics-on-css-specificity/). 47 | - Do not combine id selectors with tags (`div#container`). The id should be specific enough, and should not be repeated elsewhere to necessitate the tag name. Try to follow the same guidelines for classes as well. 48 | - When modifying an existing element for a specific use, try to use specific class names. Instead of `.listings-layout.bigger` use rules like `.listings-layout.listings-bigger`. Think about ack/greping your code in the future. 49 | - Key (rightmost) Selectors should be as specific as possible. For example `a.navigation-link` instead of `#navigation-links a`. This has [performance implications](http://www.stevesouders.com/blog/2009/06/18/simplifying-css-selectors/). 50 | 51 | ## Less Guidelines 52 | 53 | If you aren't familiar with Less, [check out the documentation](http://lesscss.org/). 54 | 55 | - Branding items such as colors and pixel measurements for font sizes should always be placed in a variable, either in `shared-package/variables.less` or local to your stylesheet where applicable. Look for a variable before defining your own. 56 | - Any `@variable` or `.mixin` that is used in more than one file should be put in the appropriate package or, if used across packages, in variables.less or mixins.less in shared-package. Others should be put at the top of the file where they're used. 57 | - Don't nest further than 4 levels deep. If you find yourself going further, think about reorganizing your rules (either the specificity needed, or the layout of the nesting). 58 | 59 | ```less 60 | /* 61 | * This is a good example of rule nesting with Less. 62 | */ 63 | .third-format { 64 | background: @transparentGray; 65 | border: 1px solid @green; 66 | color: @black; 67 | margin: 0; 68 | 69 | .next-rule { // outputs: .third-format .next-rule {...} 70 | color: @white; 71 | } 72 | 73 | &.next-rule { // outputs: .third-format.next-rule {...} 74 | color: @lightGray; 75 | } 76 | 77 | > .next-rule { // outputs: .third-format > .next-rule {...} 78 | color: @red; 79 | } 80 | } 81 | ``` 82 | 83 | - If you are creating mixins that don't take parameters in a file that is going to be imported elsewhere (e.g. `shared-package/mixins.less`), include an empty parameter list to guard against the class being output each time the file is imported. 84 | 85 | ```less 86 | .parameterless-mixin-in-an-imported-less-file() { 87 | /* The empty parameter list allows the mixin to be used without 88 | * params and prevents the Less compiler from spitting it out 89 | * each time the file is imported 90 | */ 91 | background: @transparentGray; 92 | } 93 | ``` 94 | 95 | ## Pixels vs. Ems 96 | 97 | Use `px` for `font-size`, because it offers absolute control over text. **You should almost never have to define a font-size for anything**. If you feel the need to do it, stop yourself and look through existing shared styles for an applicable class. 98 | 99 | ## File Structure 100 | 101 | If you are adding Less files to a package you are working on, add only a single Less file to the list in packages.py that imports all of the required files like this: 102 | 103 | ```scss 104 | @import "../shared-package/variables.less"; 105 | @import "../shared-package/mixins.less"; 106 | @import "my-new-package-file.less"; 107 | @import "my-second-package-file.less"; 108 | ``` 109 | 110 | With this approach, all imports are done in this file. This prevents duplications in the compiled CSS that are caused by cascading imports of the same file. 111 | -------------------------------------------------------------------------------- /gh-md-toc: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # 4 | # Steps: 5 | # 6 | # 1. Download corresponding html file for some README.md: 7 | # curl -s $1 8 | # 9 | # 2. Discard rows where no substring 'user-content-' (github's markup): 10 | # awk '/user-content-/ { ... 11 | # 12 | # 3.1 Get last number in each row like ' ... sitemap.js'. 13 | # It's a level of the current header (NF — number of fields): 14 | # substr($NF, length($NF)-1, 1) 15 | # 16 | # 3.2 Get level from 3.1 and insert corresponding number of spaces before '*': 17 | # sprintf("%*s", substr($NF, length($NF)-1, 1)*2, " ") 18 | # 19 | # 4. Find head's text and insert it inside "* [ ... ]": 20 | # substr($0, match($0, /a>.*<\/h/)+2, RLENGTH-5) 21 | # 22 | # 5. Find anchor and insert it inside "(...)": 23 | # substr($0, match($0, "href=\".*\" ")+5, RLENGTH-6) 24 | # 25 | 26 | gh_toc_version="0.4.5" 27 | 28 | gh_user_agent="gh-md-toc v$gh_toc_version" 29 | 30 | # 31 | # Download rendered into html README.md by its url. 32 | # 33 | # 34 | gh_toc_load() { 35 | local gh_url=$1 36 | 37 | if type curl &>/dev/null; then 38 | curl --user-agent "$gh_user_agent" -s "$gh_url" 39 | elif type wget &>/dev/null; then 40 | wget --user-agent="$gh_user_agent" -qO- "$gh_url" 41 | else 42 | echo "Please, install 'curl' or 'wget' and try again." 43 | exit 1 44 | fi 45 | } 46 | 47 | # 48 | # Converts local md file into html by GitHub 49 | # 50 | # ➥ curl -X POST --data '{"text": "Hello world github/linguist#1 **cool**, and #1!"}' https://api.github.com/markdown 51 | #
Hello world github/linguist#1 cool, and #1!
'" 52 | gh_toc_md2html() { 53 | local gh_file_md=$1 54 | curl -s --user-agent "$gh_user_agent" \ 55 | --data-binary @$gh_file_md -H "Content-Type:text/plain" \ 56 | https://api.github.com/markdown/raw 57 | } 58 | 59 | # 60 | # Is passed string url 61 | # 62 | gh_is_url() { 63 | if [[ $1 == https* || $1 == http* ]]; then 64 | echo "yes" 65 | else 66 | echo "no" 67 | fi 68 | } 69 | 70 | # 71 | # TOC generator 72 | # 73 | gh_toc(){ 74 | local gh_src=$1 75 | local gh_src_copy=$1 76 | local gh_ttl_docs=$2 77 | 78 | if [ "$gh_src" = "" ]; then 79 | echo "Please, enter URL or local path for a README.md" 80 | exit 1 81 | fi 82 | 83 | 84 | # Show "TOC" string only if working with one document 85 | if [ "$gh_ttl_docs" = "1" ]; then 86 | 87 | echo "Table of Contents" 88 | echo "=================" 89 | echo "" 90 | gh_src_copy="" 91 | 92 | fi 93 | 94 | if [ "$(gh_is_url $gh_src)" == "yes" ]; then 95 | gh_toc_load "$gh_src" | gh_toc_grab "$gh_src_copy" 96 | else 97 | gh_toc_md2html "$gh_src" | gh_toc_grab "$gh_src_copy" 98 | fi 99 | } 100 | 101 | GH_GREP="grep -E --null-data --text -o" 102 | if [ "`uname`" = "Darwin" ]; then 103 | GH_GREP="grep -E --text -o" 104 | fi 105 | 106 | # 107 | # Grabber of the TOC from rendered html 108 | # 109 | # $1 — a source url of document. 110 | # It's need if TOC is generated for multiple documents. 111 | # 112 | gh_toc_grab() { 113 | # find strings that corresponds to template 114 | $GH_GREP \ 115 | '` tags and most terminal tools), and no editor wraps long lines intelligently enough in all cases.
235 | > - Side-by-side code windows. Try it out, it's great.
236 | > - A free gut check. It's easy to reach 100 or 200 columns when writing complex expressions or nesting deeply. If this indicates unclear code, future readers would appreciate a quick refactoring.
237 |
238 | > Of course having a hard limit for line length is silly. Any reasonable limit runs into a case where breaking the rule produces better code. However, having unnecessarily long lines scattered about due to assumptions about a reader's tools is also silly.
239 |
240 | Python expressions end with a newline, not a semicolon, unlike many C-based languages. The trick is that lines can be continued within parentheses, brackets, and braces, or following a backslash. Parentheses are recommended. Backslashes should be avoided.
241 |
242 | ```py
243 | >>> True and (True or
244 | ... False)
245 |
246 |
247 | >>> [x * x
248 | ... for x
249 | ... in xrange(0, 10)
250 | ... if x % 2]
251 |
252 |
253 | >>> {'Earth': 1,
254 | ... 'Jupiter': 5.3,
255 | ... 'Saturn': 9}
256 | ```
257 |
258 | Notably, splitting string literals doesn't require use of the + operator. Adjacent literals are automatically combined.
259 |
260 | ```py
261 | >>> twist = 'Peter Piper ' 'split a set ' 'of simple strings'
262 | >>> twist
263 | 'Peter Piper split a set of simple strings'
264 | ```
265 |
266 | This makes splitting long messages easy.
267 |
268 | ```py
269 | >>> twist = ('Peter Piper '
270 | ... 'split a set '
271 | ... 'of simple strings')
272 | ```
273 |
274 | Because Python's indentation style is unlike many C-based languages, your editor might need some cajoling to support it.
275 |
276 | - Emacs has great support out of the box via `python-mode`.
277 | - Vim needs some help. Installing [this indent script by Eric Mc Sween](http://www.vim.org/scripts/script.php?script_id=974) will get you there.
278 |
279 | Examples of line splitting from our code:
280 |
281 | ```py
282 | BAD: zero_duration_videos = video_models.Video.all().filter("duration =", 0).fetch(10000)
283 |
284 | GOOD: zero_duration_videos = (video_models.Video.all()
285 | .filter("duration =", 0)
286 | .fetch(10000))
287 | ```
288 |
289 | ```py
290 | # BAD:
291 | return current_app.response_class("OAuth error. %s" % e.message, status=401, headers=build_authenticate_header(realm="http://www.khanacademy.org"))
292 |
293 | # GOOD:
294 | return current_app.response_class(
295 | "OAuth error. %s" % e.message, status=401,
296 | headers=build_authenticate_header(realm="http://www.khanacademy.org"))
297 | ```
298 |
299 | ```py
300 | # Bad:
301 | kwargs = dict((str(key), value) for key, value in topic_json.iteritems() if key in ['id', 'title', 'standalone_title', 'description', 'tags', 'hide'])
302 | # Good:
303 | kwargs = dict((str(key), value)
304 | for key, value in topic_json.iteritems()
305 | if key in ['id', 'title', 'standalone_title',
306 | 'description', 'tags', 'hide'])
307 | ```
308 |
309 | ## Splitting tricky lines
310 |
311 | There are cases where line splitting doesn't feel nice. Let's look at a few of them, sigh, and move on.
312 |
313 | This long method reference needs surrounding parens and splits the line before the dot operator:
314 |
315 | ```py
316 | # Bad:
317 | badge_name = badges.topic_exercise_badges.TopicExerciseBadge.name_for_topic_key_name(self.key().name())
318 | # Good:
319 | badge_name = (badges.topic_exercise_badges.TopicExerciseBadge
320 | .name_for_topic_key_name(self.key().name()))
321 | ```
322 |
323 | This long string path needs to be split.
324 |
325 | ```py
326 | # Bad:
327 | self.redirect("/class_profile?selected_graph_type=%s&coach_email=%s&graph_query_params=%s" %
328 | (self.GRAPH_TYPE, urllib.quote(coach.email), urllib.quote(urllib.quote(self.request.query_string))))
329 | # Good:
330 | self.redirect(
331 | "/class_profile?selected_graph_type=%s&coach_email=%s"
332 | "&graph_query_params=%s" % (
333 | self.GRAPH_TYPE,
334 | urllib.quote(coach.email),
335 | urllib.quote(urllib.quote(self.request.query_string))))
336 | ```
337 |
338 | Sometimes, the best way to avoid long lines is to use temporary variables. This can improve readability in any case.
339 |
340 | ```py
341 | # Bad:
342 | topics_list = [t for t in topics if not (
343 | (t.standalone_title == "California Standards Test: Algebra I" and t.id != "algebra-i") or
344 | (t.standalone_title == "California Standards Test: Geometry" and t.id != "geometry-2"))
345 | ]
346 | # Good:
347 | bad_title_and_ids = [("California Standards Test: Algebra I", "algebra-i"),
348 | ("California Standards Test: Geometry", "geometry-2"),
349 | ]
350 | topics_list = [t for t in topics
351 | if not (t.standalone_title, t.id) in bad_title_and_ids]
352 | ```
353 |
354 | ## Indenting continued lines properly
355 |
356 | When a logical statement is split into multiple lines and is followed by an indented line, the continued lines should be indented further to set them apart from the next logical statement.
357 |
358 | In the following example taken from http://www.python.org/dev/peps/pep-0008/#maximum-line-length, notice how the second and third lines of the if condition are indented further to differentiate them from the actual raise statement within the if block:
359 |
360 | ```py
361 | class Rectangle(Blob):
362 | def __init__(self, width, height,
363 | color='black', emphasis=None, highlight=0):
364 | if (width == 0 and height == 0 and
365 | color == 'red' and emphasis == 'strong' or
366 | highlight > 100):
367 | raise ValueError("sorry, you lose")
368 | ```
369 |
370 | ## Classes
371 |
372 | Use classes primarily as a container for data. You can use methods
373 | for functionality that is "core" to the purpose of a class -- one way
374 | to tell is if that method has a lot of callers, rather than just one
375 | or two -- or when needed for method-name dispatch (e.g., having a
376 | bunch of different classes define their own `get_search_data()` so a
377 | function can call `node.get_search_data()` and get the right function
378 | for that node-type), but try to keep them to a minimum. Prefer
379 | top-level functions instead.
380 |
381 | > Rationale: in Python, the implementation of a class needs to be in a
382 | single file. If you have a class with many methods
383 | doing many different things, each with its own set of dependencies,
384 | then the file defining that class ends up with a huge number of
385 | dependencies. This can make the class hard to use, since importing
386 | it may cause circular imports -- or at the least, bloat your
387 | dependency graph.
388 |
389 | For the same reason, prefer one class per file. (You can make
390 | exceptions for classes that are very closely related, or a class that
391 | is used primarily as a member of another class.)
392 |
393 |
--------------------------------------------------------------------------------
/style/android-development-best-practices.md:
--------------------------------------------------------------------------------
1 | ## Prefer Immutability
2 |
3 | Our principal goal as programmers is to reduce complexity. At any point in a program, we can reason about the state of a constant trivially -- its state is the state upon assignment. By contrast, the state of a variable can change endlessly.
4 |
5 | With judicious use, immutable objects can lead to quicker execution speed and lower memory usage. The hash value of a `String`, the query parameters of an immutable URL, and the distance traced by an immutable sequence of points are all immutable as well. We can cache their values for later use instead of recompute them on every access. We can also share an immutable object with clients without requiring those clients to defensively copy it.
6 |
7 | An immutable value is safe in many ways. For example, `String` is immutable and so its `hashCode` value is immutable. Consequently, it is safe to use as a `String` as a key in a `Map`: The lower order bits of that hash will never change, and so an inserted key will never reside in the wrong bucket. An immutable value is also thread-safe, because a client must acquire a lock only to read data that another client can concurrently modify. An immutable value renders that moot.
8 |
9 | A good approach for creating immutable types is to define types that are simply data, and which do not have behavior. For example, consider the `SecondsWatched` class, which tracks the seconds watched values in a video:
10 |
11 | ```java
12 | public class SecondsWatched {
13 | public final long lastSecondWatched;
14 | public final long totalSecondsWatched;
15 |
16 | public SecondsWatched(long lastSecondWatched, long totalSecondsWatched) {
17 | /* Preconditions checks here... */
18 |
19 | this.lastSecondWatched = lastSecondWatched;
20 | this.totalSecondsWatched = totalSecondsWatched;
21 | }
22 |
23 | /* Method equals, hashCode, and toString follow here. */
24 | }
25 | ```
26 |
27 | Note that this instance is immutable because its `lastSecondsWatched` and `totalSecondsWatched` fields are `final`, and hence cannot be reassigned.
28 |
29 | The `VideoUserProgress` instance composes a `SecondsWatched` instance of this class:
30 |
31 | ```java
32 | public final class VideoUserProgress extends ContentItemUserProgress {
33 | public final SecondsWatched secondsWatched;
34 | public final Optional lastWatchedDate;
35 |
36 | /* Constructor, equals, hashCode, and toString follow here. */
37 | }
38 | ```
39 |
40 | Again, `SecondsWatched` is immutable. So is an instance of the `Date` class. A constructed `Optional` cannot be reassigned to refer to another (possibly `null`) value, and so it is immutable as well. By virtue of the `VideoUserProgress` composing instances of immutable classes, and making those fields `final`, then each `VideoUserProgress` instance is immutable as well.
41 |
42 | Note that for such immutable classes, we do not provide getter methods for each field. Instead, these fields are have `public` visibility. This is because getter methods are typically paired with corresponding setter methods for access control, but immutable values can by definition not be set.
43 |
44 | ## Prefer interfaces over implementations
45 |
46 | The Java standard library has a rich suite of interfaces for collections: `List`, `Map`, `SortedMap`, `Set`, and `SortedSet`. The standard implementations of these interfaces include `ArrayList`, `LinkedList`, `HashMap`, `TreeMap`, `HashSet`, and `TreeSet`.
47 |
48 | For their parameters and return values, method signatures should refer to the most generalizable collection type that is most appropriate. For example, consider the `VideoSubtitleSequence` class, which represents a transcript for a video:
49 |
50 | ```java
51 | public final class VideoSubtitleSequence {
52 | private final ImmutableList subtitles;
53 |
54 | public VideoSubtitleSequence(List subtitles) {
55 | this.subtitles = ImmutableList.copyOf(subtitles);
56 | }
57 |
58 | ...
59 | }
60 | ```
61 |
62 | A transcript is an ordering of `VideoSubtitle` instances. Its constructor has a parameter type of `List`. It does not specify a list implementation, such as `ArrayList`:
63 |
64 | ```java
65 | public VideoSubtitleSequence(ArrayList subtitles) { ... }
66 | ```
67 |
68 | This constructor could not be invoked if the client had a value of type `List`, `LinkedList`, or some other `List` implementation. The client would have to create and populate a temporary `ArrayList` simply for the purposes of calling this method.
69 |
70 | Likewise, the constructor does not choose a superinterface of `List`, such as `Collection` or `Iterable`:
71 |
72 | ```java
73 | public VideoSubtitleSequence(Collection subtitles) { ... }
74 | ```
75 |
76 | Because `Set` extends `Collection`, a client could construct a `VideoSubtitleSequence` with a `Set` of `VideoSubtitle` instances. But the iteration order for a `Set` implementation is undefined. Therefore the order in which the client adds `VideoSubtitle` instances to the set is not guaranteed, or even likely, to equal the iteration order when the video plays.
77 |
78 | ## Adding a Database Table
79 |
80 | If you need to persist a new type of data to SQLite, there are quite a few steps you need to go through. Fortunately, they are all quite straightforward and self-contained.
81 |
82 | ### Define any table names
83 |
84 | Create a new class that defines the names of any tables you need to create. You must also create a `SelectStatementSource` for each of them; this allows you to later execute queries against the table with the given name.
85 |
86 | For example, the following `UserDatabaseTables` class defines a single SQLite table named `UserSession`, and creates a `SelectStatementSource` for it:
87 |
88 | ```java
89 | final class UserDatabaseTables {
90 | private UserDatabaseTables() {}
91 |
92 | static final class Names {
93 | public static final String USER_SESSION = "UserSession";
94 | }
95 |
96 | static final class Sources {
97 | public static final SelectStatementSource USER_SESSION = SelectStatementSource.table(Names.USER_SESSION);
98 | }
99 | }
100 | ```
101 |
102 | ### Define the table columns
103 |
104 | Create a new class that defines the names of all the columns in the table. Note that here you are simply defining their names; their types are defined separately in a migration below.
105 |
106 | For example, the following `UserDatabaseTableColumns` defines the columns for the `UserSession` table:
107 |
108 | ```java
109 | public final class UserDatabaseTableColumns {
110 | private UserDatabaseTableColumns() {}
111 |
112 | public static final class UserSessionTable {
113 | public static final ResultColumn KAID = ResultColumn.withName("kaid");
114 | public static final ResultColumn NICKNAME = ResultColumn.withName("nickname");
115 | ...
116 |
117 | public static final List ALL_COLUMNS = ImmutableList.of(
118 | KAID,
119 | NICKNAME,
120 | ...
121 | );
122 | }
123 | }
124 | ```
125 |
126 | As seen above, it is also typically helpful to define a static `ALL_COLUMNS` field. This is so you can easily build a `SelectStatement` that selects all columns from rows that satisfy a given condition.
127 |
128 | ### Define a migration
129 |
130 | We define a *migration* any time the schema for a given table changes. When the app is first launched on a device, the table does not exist; therefore the first migration we must perform is to actually create the table.
131 |
132 | The following shows how the migrations defined for the user database:
133 |
134 | ```java
135 | public static List getMigrations() {
136 | return ImmutableList.of(
137 | rawSql("CREATE TABLE " + Names.USER_SESSION + " (" +
138 | KAID + " TEXT PRIMARY KEY NOT NULL," +
139 | NICKNAME + " TEXT NULL," +
140 | IS_PHANTOM + " BOOLEAN NOT NULL," +
141 | AUTH_TOKEN_VALUE + " TEXT NOT NULL," +
142 | AUTH_TOKEN_SECRET + " TEXT NOT NULL," +
143 | IS_ACTIVE + " BOOLEAN NOT NULL DEFAULT 0" +
144 | ")"
145 | ),
146 | rawSql("CREATE INDEX UserSessionIsActiveIndex ON " + Names.USER_SESSION + "(" +
147 | IS_ACTIVE +
148 | ")"
149 | ),
150 | rawSql("ALTER TABLE " + Names.USER_SESSION + " ADD COLUMN " +
151 | AVATAR_URL + " TEXT NULL"
152 | )
153 | );
154 | }
155 | ```
156 |
157 | This first creates the table, and then adds an index on its `IS_ACTIVE` column. Later we decided to store the `AVATAR_URL` for a user; therefore we had to define an additional migration to add this to any existing table.
158 |
159 | Typically you only need to define a `CREATE TABLE` migration. Also you should create any `CREATE INDEX` migrations as needed.
160 |
161 | ### Define a transformer
162 |
163 | If you want to persist instances of `MyClass` to your new table, then we must specify how to convert between a `MyClass` instance and the columns in the table. This is what a transformer does. There are two directions to the transformation.
164 |
165 | When writing to the database, a `EntityToDatabaseRowTransformer` implementation converts from an instance of type `T` to the corresponding column values:
166 |
167 | ```java
168 | public interface EntityToDatabaseRowTransformer {
169 | Map transformEntityIntoRow(T data);
170 | }
171 | ```
172 |
173 | When reading from the database, a `DatabaseRowToEntityTransformer` implementation converts from the column values to an instance of type `T`:
174 |
175 | ```java
176 | public interface DatabaseRowToEntityTransformer {
177 | T transformRowIntoEntity(Map row);
178 | }
179 | ```
180 |
181 | For example, the `UserSessionEntityTransformer` implements both these interfaces, thereby defining both transformations for a `UserSession`:
182 |
183 | ```java
184 | @Override
185 | public UserSession transformRowIntoEntity(final Map row) {
186 | final User user = new User(
187 | (String) row.get(KAID.toString()),
188 | toBoolean(row.get(IS_PHANTOM.toString())),
189 | (String) row.get(NICKNAME.toString()),
190 | toUri(row.get(AVATAR_URL.toString()))
191 | );
192 |
193 | return new UserSession(
194 | new OAuthAccessToken(
195 | (String) row.get(AUTH_TOKEN_VALUE.toString()),
196 | (String) row.get(AUTH_TOKEN_SECRET.toString())
197 | ),
198 | user
199 | );
200 | }
201 |
202 | @Override
203 | public Map transformEntityIntoRow(final UserSession userSession) {
204 | final Builder builder = ImmutableNullableMap.builder();
205 |
206 | return builder
207 | .put(KAID.toString(), userSession.user.kaid)
208 | .put(NICKNAME.toString(), userSession.user.nickname.orNull())
209 | .put(IS_PHANTOM.toString(), fromBoolean(userSession.user.isPhantom))
210 | .put(AVATAR_URL.toString(), fromUriOptional(userSession.user.avatarUrl))
211 | .put(AUTH_TOKEN_VALUE.toString(), userSession.authToken.value)
212 | .put(AUTH_TOKEN_SECRET.toString(), userSession.authToken.secret)
213 | .build();
214 | }
215 | ```
216 |
217 | ### Define a database client
218 |
219 | We now have all the primitives needed to create and modify a table with values of some type `T`. To modify such a table, use the static factory methods or builders of the following classes to create corresponding SQLite statements:
220 |
221 | * `SelectStatement`
222 | * `InsertStatement`
223 | * `UpdateStatement`
224 | * `DeleteStatement`
225 |
226 | Once you have such a statement, you can execute it by passing it to the corresponding method of a `Database`. Note that if you are using a `SelectStatement`, you must pass a `DatabaseRowToEntityTransformer` parameter to convert each selected row to an instance of its corresponding type. Likewise, if you are using an `InsertStatement` or a `UpdateStatement`, you must pass a `EntityToDatabaseRowTransformer` parameter to convert each instance to the collection of column values.
227 |
228 | For example, in the `UserDatabase` class, the following `FETCH_ALL_SESSIONS_STATEMENT` is a query that selects all columns from all rows of the `UserSession` table:
229 |
230 | ```java
231 | private static final SelectStatement FETCH_ALL_SESSIONS_STATEMENT = SelectStatement.selectColumns(
232 | UserSessionTable.ALL_COLUMNS,
233 | Sources.USER_SESSION
234 | );
235 | ```
236 |
237 | The `fetchAllUserSessions` method below executes this query against a database by invoking `fetchObjects` on its `Database` instance. Its `SESSION_TRANSFORMER` instance implements `DatabaseRowToEntityTransformer`, and thereby converts each returned row to a `UserSession` instance.
238 |
239 | ```java
240 | public Set fetchAllUserSessions() {
241 | return ImmutableSet.copyOf(mDatabase.fetchObjects(
242 | FETCH_ALL_SESSIONS_STATEMENT,
243 | SESSION_TRANSFORMER
244 | ));
245 | }
246 | ```
247 |
248 | Similarly, the `insertOrUpdateUserSession` method below inserts a given `UserSession` by invoking `insertOrReplaceRows` on its `Database` instance. The `Names.USER_SESSION` specifies the table to insert into, the `ImmutableList.of` static factory method creates the list of `UserSession` instances to insert, and the `SESSION_TRANSFORMER` instance implements `EntityToDatabaseRowTransformer` and thereby converts each `UserSession` instance to its row values:
249 |
250 | ```java
251 | public void insertOrUpdateUserSession(final UserSession userSession) {
252 | mDatabase.update(InsertStatement.insertOrReplaceRows(
253 | Names.USER_SESSION,
254 | ImmutableList.of(userSession),
255 | SESSION_TRANSFORMER
256 | ));
257 | }
258 | ```
259 |
260 | ## Use Google Guava
261 |
262 | From the Google Guava web page:
263 |
264 | > The Guava project contains several of Google's core libraries that we rely on in our Java-based projects: collections, caching, primitives support, concurrency libraries, common annotations, string processing, I/O, and so forth.
265 |
266 | Some of the packages are indispensable for disciplined Java development:
267 |
268 | ### Preconditions
269 |
270 | The `Preconditions` class is found in the `com.google.common.base` package. Use it in every constructor you define to ensure that a client is constructing a valid instance. By catching such invalid data when the instance is created, we can determine the source of the invalid parameters by navigating the stacktrace. If we checked for validity upon use, we not only put the burden on every client to ensure that the data is valid, and this precludes finding where the invalid data comes from.
271 |
272 | #### Method `checkNotNull`
273 |
274 | The `checkNotNull` method accepts a parameter and throws an `NullPointerException` if it is `null`. It returns that parameter if it is not. Use it for objects:
275 |
276 | ```java
277 | public final class ApiClient {
278 | public final ContentApi contentApi;
279 | public final UserApi userApi;
280 |
281 | public ApiClient(ContentApi contentApi, UserApi userApi) {
282 | this.contentApi = Preconditions.checkNotNull(contentApi);
283 | this.userApi = Preconditions.checkNotNull(userApi);
284 | }
285 | }
286 | ```
287 |
288 | Therefore a client of an `ApiClient` instance can be ensured that it has `contentApi` and `userApi` fields that are not `null`. It does not need to defend itself against these conditions.
289 |
290 | #### Method `checkArgument`
291 |
292 | The `checkArgument` method asserts that its expression is `true`. If not, it throws an `IllegalArgumentException`. It's best to provide a second parameter that provides a more detailed message for the `IllegalArgumentException`. This message should include any parameter in the expression that evaluated as `false`. This makes debugging easier. For example:
293 |
294 | ```java
295 | public final class SecondsWatched {
296 | public final long lastSecondWatched;
297 | public final long totalSecondsWatched;
298 |
299 |
300 | public SecondsWatched(long lastSecondWatched, long totalSecondsWatched) {
301 | Preconditions.checkArgument(lastSecondWatched >= 0,
302 | "lastSecondWatched cannot be negative: " + lastSecondWatched);
303 | Preconditions.checkArgument(totalSecondsWatched >= 0,
304 | "totalSecondsWatched cannot be negative: " + totalSecondsWatched);
305 |
306 | this.lastSecondWatched = lastSecondWatched;
307 | this.totalSecondsWatched = totalSecondsWatched;
308 | }
309 | }
310 | ```
311 |
312 | ### Object utilities
313 |
314 | For immutable objects that are just data and define no behavior, it's useful to implement `equals`, `hashCode`, and `toString`.
315 |
316 | #### Implementing `equals`
317 |
318 | The template for implementing `equals` is:
319 |
320 | ```java
321 | public final class VideoSubtitle {
322 | public final long timeMillis;
323 | public final String text;
324 |
325 | @Override
326 | public boolean equals(Object o) {
327 | if (this == o) return true;
328 | if (!(o instanceof VideoSubtitle)) return false;
329 |
330 | VideoSubtitle that = (VideoSubtitle) o;
331 |
332 | return (timeMillis == that.timeMillis
333 | && text.equals(that.text));
334 | }
335 | }
336 | ```
337 |
338 | As shown above, do not forget to use the `equals` method to compare instance fields that are objects (i.e. extend `Object`). Accidentally using `==` will test for identity, or whether both operands refer to the same object (i.e. the same instance at the same address in memory).
339 |
340 | Defining an `equals` method is especially useful for testing, as it allow us to leverage the `assertEquals` method:
341 |
342 | ```
343 | long expectedTimeMillis = 123;
344 | String expectedText = "Let's count to three."
345 | VideoSubtitle expectedSubtitle =
346 | new VideoSubtitle(expectedTimeMillis, expectedText);
347 |
348 | assertEquals(expectedSubtitle, actualSubtitle);
349 | ```
350 |
351 | If these values are not equal, then `assertEquals` prints both arguments in the thrown assertion. (Which is only helpful if `toString` is implemented, as described below!)
352 |
353 | #### Implementing `equals` with inheritance
354 |
355 | If there is a base class, then the derived class `equals` implementation should call the base class `equals` implementation. For example, consider the abstract base class `ContentItemUserProgress`:
356 |
357 | ```java
358 | public abstract class ContentItemUserProgress {
359 | public final ContentItemIdentifier contentItemIdentifier;
360 | public final UserProgressLevel progressLevel;
361 |
362 | @Override
363 | public boolean equals(Object o) {
364 | if (this == o) return true;
365 | if (!(o instanceof ContentItemUserProgress)) return false;
366 |
367 | ContentItemUserProgress that = (ContentItemUserProgress) o;
368 |
369 | return (contentItemIdentifier.equals(that.contentItemIdentifier) &&
370 | progressLevel == that.progressLevel);
371 | }
372 | }
373 | ```
374 |
375 | The subclass `VideoUserProgress` tests that the instance fields in the base class are equal before testing that its own instance fields are equal:
376 |
377 | ```java
378 | public final class VideoUserProgress extends ContentItemUserProgress {
379 | public final SecondsWatched secondsWatched;
380 | public final Optional lastWatchedDate;
381 |
382 | @Override
383 | public boolean equals(Object o) {
384 | if (this == o) return true;
385 | if (!(o instanceof VideoUserProgress)) return false;
386 |
387 | if (!super.equals(o)) return false;
388 |
389 | VideoUserProgress that = (VideoUserProgress) o;
390 |
391 | return (secondsWatched.equals(that.secondsWatched) &&
392 | lastWatchedDate.equals(that.lastWatchedDate));
393 | }
394 | }
395 | ```
396 |
397 | #### Implementing `hashCode`
398 |
399 | The `Objects` method from the `com.google.common.base` package contains a `hashCode` method that computes a hash value for a list of arguments. Use it to compute the hash code of all the instance fields of a class:
400 |
401 | ```
402 | @Override
403 | public int hashCode() {
404 | return Objects.hashCode(lastSecondWatched, totalSecondsWatched);
405 | }
406 | ```
407 |
408 | #### Implementing `hashCode` with inheritance
409 |
410 | If there is a base class, then the derived class `hashCode` implementation should call the base class `hashCode` implementation. For example, again consider the base class `ContentItemUserProgress`:
411 |
412 | ```java
413 | public final class VideoUserProgress extends ContentItemUserProgress {
414 | public final SecondsWatched secondsWatched;
415 | public final Optional lastWatchedDate;
416 |
417 | @Override
418 | public int hashCode() {
419 | return Objects.hashCode(contentItemIdentifier, progressLevel);
420 | }
421 | }
422 | ```
423 |
424 | The subclass `VideoUserProgress` mixes in the superclass `hashCode` value into the `hashCode` value that it returns:
425 |
426 | ```java
427 | public final class VideoUserProgress extends ContentItemUserProgress {
428 | public final SecondsWatched secondsWatched;
429 | public final Optional lastWatchedDate;
430 |
431 | @Override
432 | public int hashCode() {
433 | return Objects.hashCode(super.hashCode(), secondsWatched, lastWatchedDate);
434 | }
435 | }
436 | ```
437 |
438 | #### Implementing `toString`
439 |
440 | The `MoreObjects` class from the `com.google.common.base` package has a useful `toStringHelper` method that, as its name implies, helps construct a `toString` value. Again, consider the `SecondsWatched` class:
441 |
442 | ```java
443 | public class SecondsWatched {
444 | public final long lastSecondWatched;
445 | public final long totalSecondsWatched;
446 |
447 | @Override
448 | public String toString() {
449 | return MoreObjects.toStringHelper(this)
450 | .add("lastSecondWatched", lastSecondWatched)
451 | .add("totalSecondsWatched", totalSecondsWatched)
452 | .toString();
453 | }
454 | }
455 | ```
456 |
457 | If `SecondsWatched` is instantiated with a `lastSecondWatched` value of `5` and a `totalSecondsWatched` value of `10`, then invoking its `toString` method will return:
458 |
459 | ```
460 | SecondsWatched={lastSecondWatched=5, totalSecondsWatched=10}
461 | ```
462 |
463 | #### Implementing `toString` with inheritance
464 |
465 | If there is a base class, it may be helpful for it to provide a `protected` factory method that creates a `ToStringHelper` instance and adds to it the base class fields. The derived class implementation of `toString` can then invoke this base class factory method and add to it the derived class fields before returning. For example, again consider the base class `ContentItemUserProgress`:
466 |
467 | ```java
468 | public abstract class ContentItemUserProgress {
469 | public final ContentItemIdentifier contentItemIdentifier;
470 | public final UserProgressLevel progressLevel;
471 |
472 | protected MoreObjects.ToStringHelper getToStringHelper() {
473 | return MoreObjects.toStringHelper(this)
474 | .add("contentItemIdentifier", contentItemIdentifier)
475 | .add("kind", getItemKind())
476 | .add("progressLevel", progressLevel);
477 | }
478 | }
479 | ```
480 |
481 | The subclass `VideoUserProgress` then adds its own fields to the `ToStringHelper` before returning:
482 |
483 | ```java
484 | public final class VideoUserProgress extends ContentItemUserProgress {
485 | public final SecondsWatched secondsWatched;
486 | public final Optional lastWatchedDate;
487 |
488 | @Override
489 | public String toString() {
490 | return getToStringHelper()
491 | .add("secondsWatched", secondsWatched)
492 | .add("lastWatchedDate", lastWatchedDate)
493 | .toString();
494 | }
495 | }
496 | ```
497 |
498 | ### Immutable Collections
499 |
500 | As described earlier, immutability is a critical aspect of controlling complexity in our programs. As such, for each of the collection interfaces `List`, `Map`, `SortedMap`, `Set`, and `SortedSet`, Guava defines an implementation whose instances are immutable. This is only possible because the mutating methods of each collection interface, such as `add`, `set`, or `remove`, are defined as optional operations. The underlying implementation can choose to implement the method as specified by the interface, or throw an `UnsupportedOperationException`. The collections defined by Guava choose the latter.
501 |
502 | > The standard libraries for some languages, such as Objective-C and Scala, instead define separate types for mutable and immutable collections.
503 |
504 | #### Construction from individual elements
505 |
506 | To construct an immutable collection from individual elements, invoke the correct overload form of its `of` static factory method:
507 |
508 | ```java
509 | public static final List ALL_COLUMNS = ImmutableList.of(
510 | CONTENT_KEY,
511 | Videos.VIDEO_SIZE,
512 | Videos.VIDEO_STATUS,
513 | Videos.TRANSCRIPT_SIZE,
514 | Videos.TRANSCRIPT_STATUS
515 | );
516 | ```
517 |
518 | #### Construction from another collection
519 |
520 | To construct an immutbale collection with the contents of an collection, such as an `Iterable` or an array, call the correct overload of its `copyOf` static factory method:
521 |
522 | ```java
523 | public final class NodeTree {
524 | public final List nodes;
525 | public final Set relationships;
526 |
527 | public NodeTree(final List nodes, final Set relationships) {
528 | this.nodes = ImmutableList.copyOf(nodes);
529 | this.relationships = ImmutableSet.copyOf(relationships);
530 | }
531 |
532 | ...
533 | }
534 | ```
535 |
536 | #### Construction with a builder
537 |
538 | Finally, if you cannot statically construct such an immutable instance, use an instance of its corresponding [builder](https://en.wikipedia.org/wiki/Builder_pattern) class. Such an instance is mutable and accumulates elements. Calling `build` constructs an immutable instance with those accumulated elements:
539 |
540 | ```java
541 | public static List readSubtitles(JsonReader reader) throws IOException {
542 | ImmutableList.Builder builder = ImmutableList.builder();
543 |
544 | reader.beginArray();
545 | while (reader.hasNext()) {
546 | builder.add(VideoSubtitleJsonDecoder.read(reader));
547 | }
548 | reader.endArray();
549 |
550 | return builder.build();
551 | }
552 | ```
553 |
554 | #### Defensive copying
555 |
556 | A class should defensively copy any collection instance that it is injected with. For example, consider the `JavaScriptCommand` class:
557 |
558 | ```java
559 | public final class JavaScriptCommand {
560 | public final String methodName;
561 | public final List parameters;
562 |
563 | public JavaScriptCommand(String methodName, List parameters) {
564 | this.methodName = Preconditions.checkNotNull(methodName);
565 | this.parameters = ImmutableList.copyOf(parameters);
566 | }
567 |
568 | ...
569 | }
570 | ```
571 |
572 | If a client constructs a `JavaScriptCommand` instance, and then mutates the `List` instance that it passed in as the `parameters` value, then the `JavaScriptCommand` instance will not reflect those mutations. By defensively copying, it enforces its immutability.
573 |
574 | Note that the `copyOf` method above will use reflection to test whether its parameter is also an `ImmutableList` instance. If so, then it simply returns its parameter instead of constructing a new `ImmutableList` instance. This is because we must only make defensive copies to guard against unexpected mutations, but immutable instances by definition do not afford that.
575 |
576 | Consequently, the following test will pass:
577 |
578 | ```java
579 | List original = ImmutableList.of("foo", "bar");
580 | List copy = ImmutableList.copyOf(original);
581 |
582 | assertSame(original, copy);
583 | assertTrue(original == copy);
584 | ```
585 |
586 | #### Guarding aginst `null`
587 |
588 | Finally, `ImmutableList` and `ImmutableSet` prohibit `null` elements, while `ImmutableMap` prohibits `null` keys or values. Attempting to construct such an instance will throw a `NullPointerException`. If you require an `ImmutableMap` that permits `null` values, we have an `ImmutableNullableMap` instance. But before building a `List` or `Set` analogue, thoroughly consider alternative designs.
589 |
590 | ### Using `Optional`
591 |
592 | We have seen how `checkNotNull` ensures that instances are not mistakenly constructed with `null` values. But if a parameter value or a return value belonging to a method signature can be `null`, then we precede its type with the `@Nullable` annotation. For example:
593 |
594 | ```java
595 | public static @Nullable URI toUri(final @Nullable String value) throws URISyntaxException {
596 | return (value != null) ? new URI(value) : null;
597 | }
598 | ```
599 |
600 | We can even annotate variable declarations:
601 |
602 | ```java
603 | @Nullable URI downloadUrl = toUri(downloadUrlString);
604 | ```
605 |
606 | In more modern languages, *nullability* is encoded by the type system. In Swift, for example, a type suffixed with `?` can assume `null`, while a type that stands alone cannot:
607 |
608 | ```swift
609 | let x: String = "foo"
610 | let y: String? = null
611 |
612 | let z: String = null // compilation error
613 | ```
614 |
615 | In Java, The `Optional` class from Guava is a library-level imitation of this concept. It is not as robust and so it is not a substitute. But nonetheless it succeeds in forcing us to account for `null` values at compile-time.
616 |
617 | For example, consider an `toIso8601` method that converts a `Date` parameter to a string in ISO 8601 format. It expects that its `Date` parameter is not `null`, and even uses `Preconditions.checkNotNull` to assert this.
618 |
619 | A client may store the date at which a video was last watched by the user. It could convert the corresponding `Date` instance to ISO 8601 like so:
620 |
621 | ```java
622 | Date lastWatchedDate = ...
623 | String lastWatchedIso8601 = toIso8601(lastWatchedDate);
624 | ```
625 |
626 | But this does not capture that `lastWatchedDate` can be `null` if the user has never watched the corresponding video before. Consequently, `toIso8601` will be invoked with a `null` parameter, which in turn will cause its `Preconditions.checkNotNull` statement to throw a `NullPointerException` at runtime.
627 |
628 | By representing `lastWatchedDate` as an `Optional`, we can force accounting for the nullability of `lastWatchedDate` at compile-time. The client must explicitly test for the presence of a contained `Date` value before extracting it from the optional and passing it to the `toIso8601` method:
629 |
630 | ```java
631 | Optional lastWatchedDate = ...
632 | @Nullable String lastWatchedString = lastWatchedDate.isPresent()
633 | ? toIso8601(lastWatchedDate.get())
634 | : null;
635 | ```
636 |
637 | ### Using `Predicates` and `Functions`
638 |
639 | TODO
640 |
641 | ### Using `Lists` and `Maps`
642 |
643 | TODO
644 |
645 | ### Using `FluentIterable`
646 |
647 | TODO
648 |
649 |
--------------------------------------------------------------------------------
/style/go.md:
--------------------------------------------------------------------------------
1 | # Go
2 |
3 | Our style guide is based on the advice in [Effective
4 | Go](https://golang.org/doc/effective_go.html) and the [Code Review
5 | Comments wiki](https://github.com/golang/go/wiki/CodeReviewComments),
6 | with the following additions and modifications.
7 |
8 | ## Naming
9 |
10 | ### Module names with multiple words should use snake_case
11 |
12 | According to "Effective Go," we should use short package names, that
13 | are a single word. For our library code under pkg/, we follow that
14 | recommendation.
15 |
16 | But in our services/ tree, where our packages are all internal, we
17 | have a little bit more freedom (and a lot more packages to name). In
18 | some cases multi-word package names are clearer/less ambiguous, and in
19 | those cases, separate the words with underscores.
20 |
21 | ### Use a leading underscore for file-private symbols
22 |
23 | In addition to the standard Go convention that capitalized names are
24 | public and lowercased names are visible to all files in the same
25 | package, we add an additional rule: if you want to declare that only
26 | _this_ file should access the symbol, name it with a leading
27 | underscore.
28 | ```go
29 | // style/visibility.go
30 | package style
31 | func AnyoneCanCall() {}
32 | func anyFileInPackageStyleCanCall() {}
33 | func _visibilityDotGoCanCall() {}
34 | ```
35 |
36 | The same convention applies to type names and global
37 | variables/constants. For struct fields and method names, an
38 | underscore is a hint that the name is somehow private even within the
39 | package, but this is not enforced.
40 |
41 | Note that we allow tests in the same package to access file-private
42 | symbols, but tests in different packages must follow the rules and
43 | only access exported symbols.
44 |
45 | The reason for this rule is to help code readers understand the scope
46 | of code as they're reading it. It's very common to write helper
47 | functions that are only used once -- maybe twice -- and by another
48 | function in the same file. Marking those helper functions tells
49 | people reading the code not to try to make sense of an awkward API, or
50 | generically named function, or lacking documentation: looking at the
51 | nearby caller would be more productive instead.
52 |
53 | ### Initialisms
54 |
55 | Go
56 | [chooses](https://github.com/golang/go/wiki/CodeReviewComments#initialisms)
57 | naming like `RequestURL`, rather than `RequestUrl`, for initialisms
58 | like `URL`. (Prefixes like `urlPath` are not capitalized at all.) We
59 | follow this but not religiously; if multiple initialisms appear
60 | consecutively (`NWEAMAPID`), do what seems clearest in context.
61 |
62 |
63 | ## Formatting
64 |
65 | Most issues of formatting are covered by gofmt -- we actually use
66 | [gofumpt](https://github.com/mvdan/gofumpt) -- but we add a few
67 | additional rules.
68 |
69 | ### Line length
70 |
71 | Neither Go nor gofmt specify a maximum line length. We do: most lines
72 | should be at most 79 characters, and all lines must be at most 100
73 | characters.
74 |
75 | Specifically, code should be wrapped around 79 characters, but a few
76 | over is okay if there's not a great way to wrap the line. Comments
77 | must always be wrapped at 79, unless they have non-breaking words like
78 | urls, that are longer.
79 |
80 | Go is liberal in allowing line breaks without explicit continuation.
81 | Any binary operator that ends a line signals that the next line
82 | continues the same expression. For example:
83 | ```go
84 | // bad
85 | if myLongCondition("isToo", long) && yourEvenLongerCondition("has", "so", "many", "arguments") {
86 | …
87 | }
88 |
89 | // good
90 | if myLongCondition("isToo", long) &&
91 | yourEvenLongerCondition("has", "so", "many", "arguments") {
92 | …
93 | }
94 | ```
95 |
96 | In general, this style is preferable to breaking up a single element
97 | of the condition. When possible, break in a way that matches the
98 | precedence of the operators:
99 | ```go
100 | // bad
101 | if aa && ab && ac || ba &&
102 | bb && bc {
103 | …
104 | }
105 |
106 | // good
107 | if aa && ab && ac ||
108 | ba && bb && bc {
109 | ...
110 | }
111 |
112 | // ok
113 | if aa &&
114 | ab &&
115 | ac ||
116 | ba &&
117 | bb &&
118 | bc {
119 | ...
120 | }
121 | ```
122 | As you can see, gofmt's indentation will help you out in some of the
123 | awkward cases. Similar style applies to other operators like `+`, and
124 | even the field/method-lookup operator `.`. (Of course in reality, this
125 | code would benefit from some parentheses, which will also help you get
126 | the wrapping right.)
127 |
128 | For function/method definitions, if the signature is too long for a
129 | single line, put each argument on its own line (note you will need a
130 | trailing comma):
131 | ```go
132 | // bad
133 | func (t *MyType) MyBadMethod(ctx context.Context, argumentOne string, argumentTwo string) (string, error) {
134 | ...
135 | }
136 |
137 | func (t *MyType) MyBadMethod(
138 | ctx context.Context, argumentOne string, argumentTwo string,
139 | ) (string, error) {
140 | ...
141 | }
142 |
143 | // good
144 | func (t *MyType) MyGoodMethod(
145 | ctx context.Context,
146 | argumentOne string,
147 | argumentTwo string,
148 | ) (string, error) {
149 | ...
150 | }
151 |
152 | func (t *MyType) MyOtherGoodMethod(
153 | ctx context.Context,
154 | argumentOne, argumentTwo string,
155 | ) (string, error) {
156 | ...
157 | }
158 | ```
159 | It is acceptable to put multiple arguments on the same line if they
160 | use the `nameOne, nameTwo type` style, but otherwise either the
161 | signature should be a single line, or each argument should have its
162 | own line. The same wrapping is possible, albeit rarely necessary, for
163 | receivers and returns:
164 | ```go
165 | func (
166 | t *MyVeryLongReceiverTypeName,
167 | ) MyVeryLongMethodName(
168 | ctx context.Context,
169 | ) (
170 | MyVeryLongReturnTypeName,
171 | MyOtherVeryLongReturnTypeName,
172 | ) {
173 | ...
174 | }
175 | ```
176 |
177 | Sometimes, the best way is a temporary variable:
178 | ```go
179 | // bad
180 | donationAsk.DefaultDonationFrequency = donationFrequencyGraphQLtoModel[*input.DefaultDonationFrequency]
181 |
182 | // good
183 | freq := *input.DefaultDonationFrequency
184 | donationAsk.DefaultDonationFrequency = donationFrequencyGraphQLtoModel[freq]
185 | ```
186 |
187 | A few lines get exceptions to even the 100 character rule, because
188 | there's no good way to wrap them. These include machine-readable
189 | comments (like //go:generate), lines ending with a URL, or lines
190 | containing struct tags.
191 | ```go
192 | // ok
193 | var serviceURL = "https://www.my.long.service.domain.name/some/path/to/api/v1/my/call/yikes/"
194 |
195 | type myStruct struct {
196 | MyLongFieldName map[MyLongFieldKeyType]MyLongFieldValueType `json:"myLongFieldName" datastore:"my_long_field_name"`
197 | }
198 |
199 | //go:generate go run github.com/Khan/mypackage/path/to/codegen arguments --long-path=/path/to/my/directory
200 | ```
201 |
202 | ### Leave a blank line between toplevel functions
203 |
204 | This is one detail gofmt doesn't specify. We choose to leave a blank
205 | line before and after each toplevel function. Other toplevel
206 | declarations, like `var`, `const`, and `type`, may go on consecutive
207 | lines to each other, especially if they are related and fit on a
208 | single line.
209 |
210 | ```go
211 | // good
212 | type fooString string
213 | type barString string
214 |
215 | type bazString string
216 | var baz bazString
217 |
218 | var host = "www.khanacademy.org"
219 | var hostOverride string
220 |
221 | func DoSomething() {
222 | ...
223 | }
224 |
225 | func somethingElse() {
226 | ...
227 | }
228 |
229 | // ok
230 | type fooString string
231 | var unrelated = 2
232 |
233 | type fooInnerStruct {
234 | bar string
235 | baz int
236 | }
237 | type fooOuterStruct {
238 | fooInnerStruct
239 | qux map[string]string
240 | }
241 |
242 | // bad
243 | type notLikeThis string
244 | func Yuck() {
245 | ...
246 | }
247 | func yuckier() {
248 | ...
249 | }
250 | var dontDoThis string
251 | ```
252 |
253 |
254 | ## Documentation
255 |
256 | The advice in [Effective
257 | Go](https://golang.org/doc/effective_go.html#commentary) applies here
258 | too. For all godoc-based documentation, it can be useful to check how
259 | it renders in godoc on the commandline (`go doc -all ./my/pkg`) or in
260 | a browser (`godoc -http=localhost:6060`).
261 |
262 | ### Documenting packages and files
263 |
264 | Packages may be documented with a README.md, or a godoc package
265 | comment. Prefer godoc if the package's contents and consumers are all
266 | Go code (such as `pkg/mypackage` or `services/my-service/testutil`)
267 | and README if not (such as `services/my-service` or
268 | `services/my-service/my-data-files`). The formatting in godoc is not
269 | as expressive as markdown, so packages where complex formatting is
270 | particularly useful to the documentation may wish to have a godoc and
271 | a README, which reference each other.
272 |
273 | Package comments may be at the top of any Go file, before the `package
274 | mypackage`. Typically, they should go in the main entrypoint (often
275 | called `mypackage.go`, or sometimes `api.go` or `client.go`).
276 | Particularly long package comments can go in their own file,
277 | canonically `doc.go`. Only one file should have a package comment (Go
278 | doesn't require this, but it's a bit confusing otherwise.)
279 |
280 | Package comments or READMEs should include a general description of
281 | the package for someone unfamiliar with it, and general pointers as to
282 | how to use it. For example, they may point to the most common
283 | entrypoint functions or types, or give a usage example. They may also
284 | include any other useful documentation that doesn't pertain to a
285 | specific type or function. Package comments may include
286 | section-headings, which are simply text on a line by itself with no
287 | punctuation.
288 |
289 | Go has no convention for file-level documentation. We choose to put
290 | such documentation after the package comment, before the imports.
291 | This documentation should contextualize this file within the package,
292 | and is intended for other implementers (or people trying to understand
293 | the code) moreso than for the package's consumers. All non-test files
294 | in a package should have a file comment, unless the package is only a
295 | single (non-test) file.
296 |
297 | ### Documenting types, functions, methods, and fields
298 |
299 | As Effective Go says, all exported names (types, functions, methods,
300 | fields, etc.) should have godoc comments. Such comments start with a
301 | summary, which begins "FunctionName gets/does/returns/etc. …" and uses
302 | full sentences to give a short (1-2 line) summary of the function. If
303 | more information is useful, leave a blank comment-line, then further
304 | comments. Doc-comments need not repeat the signature of the function
305 | (or type of the field), except as to document arguments or returns not
306 | obvious from the name and type.
307 |
308 | Use [named
309 | returns](https://golang.org/doc/effective_go.html#named-results) if
310 | the types are not sufficient to tell what the return values mean; this
311 | is especially useful if multiple of them have the same type:
312 | ```go
313 | // bad
314 | func GetEmailBody(...) (string, string, error) { … }
315 |
316 | // good
317 | func GetEmailBody(...) (text, html string, err error) { … }
318 | ```
319 |
320 | Methods which are exclusively used via an interface (such as exported
321 | methods of unexported types) should be documented, but the
322 | documentation may simply refer to the interface, e.g. "MyMethod
323 | implements the interface MyInterface". If the method may also be used
324 | on its own, or there is more to add about this type's particular
325 | implementation of the interface, that can be included as well.
326 |
327 | Unexported names should often be documented, too, for other
328 | implementers of the package. You can see the documentation with `go
329 | doc -u`, or by reading the file.
330 |
331 |
332 | ## Imports
333 |
334 | ### Always qualify imports, with their package name if possible
335 |
336 | Go has three styles of import: you can import a package with its
337 | default name (the last component of its path), with your own name, or
338 | unqualified (by using the special name "."). Whenever possible, use
339 | the default name; this makes it easier for readers to know what code
340 | is referenced without reading your import block. If this name is not
341 | acceptable (e.g. it's very long, not meaningful, not a valid
342 | identifier, or collides with another name), it's okay to use an
343 | abbreviated name, but don't abbreviate so far that the name is
344 | nonspecific. (If the package is first-party, a better name may be in
345 | order!) Do not import unqualified. For example:
346 | ```go
347 | import (
348 | // good
349 | "net/http"
350 | "cloud.google.com/go/datastore"
351 | "github.com/Khan/webapp/pkg/lib"
352 | cloudkms "cloud.google.com/go/kms/apiv1"
353 | userlib "github.com/Khan/webapp/pkg/user"
354 | mobileData "github.com/Khan/webapp/services/mobile-data"
355 |
356 | // ok only if there are naming collisions or
357 | // other problems with "lib"
358 | khanPkgLib "github.com/Khan/webapp/pkg/lib"
359 |
360 | // bad
361 | l "github.com/Khan/webapp/pkg/lib" // nonspecific
362 | . "github.com/Khan/webapp/pkg/lib" // unqualified
363 | )
364 | ```
365 |
366 | ### Sort imports into stdlib, first-party, and third-party
367 |
368 | Standard go style requires that all imports use a single import
369 | statement, but allows blank lines to separate groups of imports within
370 | that. We follow a style similar to python: standard library imports,
371 | third-party imports, and first-party imports go in separate blocks.
372 | We consider "pkg" imports from webapp to be first-party imports. Each
373 | block is sorted (goimports enforces this). For example:
374 | ```go
375 | // good
376 | import (
377 | "log"
378 | "net/http"
379 | "os"
380 |
381 | "github.com/99designs/gqlgen/handler"
382 |
383 | "github.com/Khan/webapp/pkg/lib"
384 | "github.com/Khan/webapp/pkg/web"
385 | "github.com/Khan/webapp/services/myservice"
386 | "github.com/Khan/webapp/services/myservice/mypkg"
387 | )
388 |
389 | // bad
390 | import (
391 | "github.com/99designs/gqlgen/handler"
392 | "github.com/Khan/webapp/pkg/lib"
393 |
394 | "github.com/Khan/webapp/pkg/web"
395 | "github.com/Khan/webapp/services/myservice"
396 | "github.com/Khan/webapp/services/myservice/mypkg"
397 | "log"
398 |
399 | "net/http"
400 | )
401 | ```
402 |
403 |
404 | ## Best practices
405 |
406 | ### Be sparing in using assignments in if statements
407 |
408 | Go lets you do -- perhaps encourages you to do -- code like this:
409 | ```go
410 | // bad
411 | if value, err := myfunc(); err != nil { ... }
412 | ```
413 | This hides the important part -- calling `myfunc()` and assigning its
414 | return-type to a new local variable -- in the middle of the line, with
415 | lots of other stuff going on in that line as well.
416 |
417 | Clearer is to do the assignment separately, even though it's an extra
418 | line of code:
419 | ```go
420 | // good
421 | value, err := myfunc()
422 | if err != nil { ... }
423 | ```
424 |
425 | ### Do not use unadorned returns
426 |
427 | If you use [named
428 | returns](https://golang.org/doc/effective_go.html#named-results) for a
429 | function, Go allows you to just say `return` and it will automatically
430 | return the named variables. Opinions differ about whether this helps
431 | or hurts readability. We follow the advice on the
432 | [wiki](https://github.com/golang/go/wiki/CodeReviewComments#named-result-parameters):
433 | say `return , ` as if the return values were not named,
434 | unless the function is very short.
435 |
436 | ### Construct URLs using net/url, not string methods
437 |
438 | In general, avoid using string methods to manipulate URLs, such as by
439 | making a relative URL absolute: it's a bug-magnet as an extra or
440 | missing slash can totally break the URL. Instead, parse the URLs
441 | using `net/url.Parse`, and manipulate the `net/url.URL` objects. This
442 | can be a bit more verbose but it's much safer.
443 |
444 | The same applies for file paths: use `path/filepath`, not string
445 | manipulation.
446 |
447 | For example:
448 | ```go
449 | // bad
450 | return "https://" + req.Host + "/about"
451 |
452 | // good
453 | aboutPageURL := url.URL{
454 | Scheme: "https",
455 | Host: req.Host,
456 | Path: "/about",
457 | }
458 | return url.String()
459 | ```
460 |
461 | ### Use reflection sparingly, especially in application code
462 |
463 | Go's reflect library is useful and powerful, but it makes for much
464 | harder-to-read and less type-safe code. We do sometimes need it,
465 | especially in infrastructure code, but it's best to avoid if it's not
466 | really necessary. Definitely don't use reflect if all you need is a
467 | type switch!
468 |
469 | ### Do not be afraid to use codegen
470 |
471 | It is a great way to get type-safety, at least until generics come along.
472 |
473 |
474 | ## Concurrency
475 |
476 | Go makes writing good concurrent code easier than some languages, but
477 | there are still some rough edges and style rules to be aware of.
478 |
479 | ### Use goroutines when *you* have concurrent work to do
480 |
481 | In some languages, it's common to write functions which return a
482 | Promise (or similar), to allow the caller to wait on the result. In
483 | Go, there is no need: there is no "blocking the event loop" to worry
484 | about, and if the caller wants to do something else while they wait,
485 | they can simply use their own goroutine. So all functions should
486 | typically be written to simply return their result, not a
487 | promise-style structure.
488 |
489 | For more on this topic, see [this
490 | talk](https://drive.google.com/file/d/1nPdvhB0PutEJzdCq5ms6UI58dp50fcAN/view).
491 | ```go
492 | // good: doesn't use a goroutine
493 | func GetUserData(ctx ...) (*UserData, error) {
494 | err := ctx.Datastore().Get(...)
495 | return userData, err
496 | }
497 |
498 | // good: uses goroutines to do two things in parallel
499 | func GetUserSettingsAndUserData(ctx ...) (*UserData, *UserSettings, error) {
500 | g, ctx := errgroup.WithContext(ctx)
501 | g.Go(func() error {
502 | userData, err = GetUserData(ctx)
503 | return err
504 | })
505 | g.Go(func() error {
506 | userSettings, err = GetUserSettings(ctx)
507 | return err
508 | })
509 | err := g.Wait()
510 | return userData, userSettings, err
511 | }
512 |
513 | // bad: forces concurrency onto its caller
514 | func GetUserData(ctx ...) (<-chan *UserData, error) {
515 | ...
516 | }
517 | ```
518 |
519 | > [Khan-specific: Instead of ErrGroup or -- gasp! -- WaitGroup,
520 | > use `pkg/external/opentelemetry/tracegroup`. Its API is
521 | > similar to ErrGroup but it takes care of promoting the context
522 | > for you, and also automatically sends traces for each goroutine.]
523 |
524 | ### Arrange for all your goroutines to exit
525 |
526 | If you start a goroutine, it's your responsibility to arrange for it
527 | to exit, when this request ends or otherwise, unless it's really
528 | intended to go forever.
529 |
530 | The easiest, and recommended, way to do this for most users is with
531 | `x/sync/errgroup`. This package makes it easy to start a bunch of
532 | pieces of work, wait for them all to complete, and early-exit if any
533 | fail. Make sure to use `errgroup.WithContext` to set up the errgroup,
534 | and pass the returned context into each goroutine.
535 |
536 | Generally, make sure to always pass a context through to your callees.
537 | This helps ensure that if a request is cancelled, all its work will
538 | quickly exit. If you're doing something that might take a while, make
539 | sure you check for context-cancellation. Typically the low-level APIs
540 | will do that for you, so you just need to pass them a context, but if
541 | you are doing something slow that doesn't handle context, you'll need
542 | to do so yourself, typically with a `select` statement.
543 |
544 | To explicitly set a timeout on some work (in a goroutine or not), use
545 | `context.WithTimeout` or `context.WithDeadline` to set a deadline on
546 | the context which will be used for that work; then follow the
547 | instructions in the previous paragraph to ensure that deadline is
548 | observed. See their documentation for more.
549 |
550 | > [Khan-specific: For work that may outlive the request (such as
551 | > "fire-and-forget" RPCs), use `pkg/kacontext.Detach` along with a
552 | > goroutine; see its documentation for details. Note that such work is
553 | > not guaranteed to happen, for example if the instance is no longer
554 | > needed; use tasks for larger or more important blocks of asynchronous
555 | > work.]
556 |
557 | ### Handle panics inside goroutines
558 |
559 | When starting a goroutine, make sure you call a defer that handles any
560 | panics, before doing anything else. This ensures that a a panic in
561 | your goroutine will fail just this request, and not the whole
562 | webserver.
563 |
564 | > [Khan-specific: use `defer log.PanicHandler(ctx.Log())` for this.]
565 |
566 | ### If in doubt, use a lock
567 |
568 | If you enjoy thinking about concurrency, you'll find the Go
569 | specification as when it's safe to read and write to shared memory
570 | very interesting. In general, avoid using this knowledge: if you have
571 | to think carefully to figure out whether you need a lock to share
572 | memory safely, you probably need a lock.
573 |
574 | ### You might not need channels
575 |
576 | Channels are a great tool, and are often useful for returning results
577 | back from work happening in a goroutine. However, using channels
578 | means paying attention to when they might block, and making sure to
579 | close them if needed. Not all concurrent code needs channels.
580 | ```go
581 | // good: needs channels to do a select
582 | c := make(chan int)
583 | go func{
584 | result := ... // some work that can't be interrupted
585 | c <- result
586 | close(c)
587 | }()
588 | select {
589 | case <-ctx.Done():
590 | return "", ctx.Err() // cancelled or timed out
591 | case val := <-c:
592 | return val // operation completed
593 | }
594 |
595 | // good: doesn't need channels
596 | results := make([]int, len(inputs))
597 | g, ctx := errgroup.WithContext(ctx)
598 | for i, input := range inputs {
599 | g.Go(func() error {
600 | results[i] = f(input)
601 | return nil
602 | })
603 | }
604 | err := g.Wait()
605 | return results, err
606 |
607 | // bad: uses channels unnecessarily, which leads to several bugs!
608 | c := make(chan int) // unbuffered channel
609 | g, ctx := errgroup.WithContext(ctx)
610 | for i, input := range inputs {
611 | g.Go(func() error {
612 | c <- f(input) // waits until receive (range c below)
613 | return nil
614 | })
615 | }
616 | err := g.Wait() // waits until goroutines finish -- DEADLOCK
617 | var results []int
618 | for val := range c { // waits until channel is closed (it never is)
619 | results = append(results, val)
620 | }
621 | return results, err
622 | ```
623 |
624 | ## Contexts
625 |
626 | > [Khan-specific: Note that most Khan Academy code uses the enriched
627 | > KAContext system described by ADR-229 rather than ordinary Go
628 | > context; see pkg/kacontext for further documentation. The below
629 | > rules apply to both Go-style context and KA-style context.]
630 |
631 | ### Context should be named `ctx` and the first argument
632 |
633 | If a function needs to be passed a `context.Context`, that context
634 | should always be the first argument and should be named `ctx`.
635 | Variables referring to context should generally be named `ctx`.
636 |
637 | > [Khan-specific: if the code needs to distinguish between Go-context
638 | > and KA-context, such as the caller of `kacontext.Upgrade`, it may use
639 | > `ktx` for the KA-context.]
640 |
641 | ### Only use `context.Background` in special circumstances
642 |
643 | It's easy to use `context.Background` because you don't have to pass
644 | anything around. But unless you're spawning something that you don't
645 | want canceled if the current request is canceled, like a long-lived
646 | thread, it's the wrong thing to use. Pass around an actual context
647 | object instead.
648 |
649 | This rule does not apply to tests, nor to `main` or `init` functions,
650 | where you aren't necessarily within a request or any other meaningful
651 | context.
652 |
653 | ### Do not store context in a struct
654 |
655 | The package documentation says:
656 |
657 | > Do not store Contexts inside a struct type; instead, pass a Context
658 | > explicitly to each function that needs it.
659 |
660 | Other than a few special cases in library code where there is no good
661 | alternative, it's inadvisable to store context in a struct: the caller
662 | may need to modify that context, which gets to be a mess as the struct
663 | gets nested. Instead, pass the context explicitly.
664 |
665 | ## Google libs
666 |
667 | ### Low-level functions that modify datastore should take a Transaction object or Client object
668 |
669 | If a low-level function -- one not immediately called by an API
670 | handler or graphql resolver -- needs to `put()` to the datastore, it
671 | should explicitly take in a `datastore.Transaction` object, if the
672 | `put()` needs to be transactional, or a `ctx` that includes
673 | `datastore.KAContext`, if it does not. (A `put()` needs to be
674 | transactional if two things are being put in the same function, and we
675 | must have both-or-neither semantics; or if the helper function does
676 | get()-modify-put().) This is how the function documents whether it
677 | needs to take place in a transaction or not.
678 |
679 | ### Do not use get-or-insert semantics for read-only functions
680 |
681 | The Python datastore library has a `get_or_insert()` function, which
682 | given a key either returns an existing item from the datastore, or
683 | creates a new one if the key is not found. Do not use that idiom for
684 | code that is semantically read-only -- that is, for code that doesn't
685 | promise that the key exists in the datastore after it exits.
686 | (Accessors, such as `getFoo()`, fall into this category.) Instead
687 | just return a default entity on key-miss without actually inserting it
688 | into the datastore.
689 |
690 | ### Return a NotFound error on datastore lookup failures, rather than nil
691 |
692 | Treat not-found as an error, not as an "expected" case that returns a
693 | nil pointer. This way, downstream clients don't have to check for
694 | nil-ness in addition to checking for errors (but can still check
695 | separately for not-found vs other errors if they care to).
696 |
697 | Good:
698 | ```go
699 | func GetMyModel(key *datastore.Key) (*MyModel, error) {
700 | var mymodel MyModel
701 | err := ctx.Datastore().Get(ctx, key, &mymodel)
702 | return &mymodel, err
703 | }
704 | ```
705 |
706 | Bad:
707 | ```go
708 | func GetMyModel(key *datastore.Key) (*MyModel, error) {
709 | var mymodel MyModel
710 | err := ctx.Datastore().Get(ctx, key, &mymodel)
711 | if errors.Is(err, datastore.EntityNotFound) {
712 | return nil, nil
713 | }
714 | return &mymodel, err
715 | }
716 | ```
717 |
718 | ## Testing
719 |
720 | > [Khan-specific: this entire section is Khan-specific]
721 |
722 | ### Use the Khan Suite instead of Testify's
723 |
724 | Our tests should always use `servicetest.Suite` (or, for low-level
725 | tests, `khantest.Suite`) rather than Testify's version. We use suites
726 | to support tear-down functionality, extra methods, and for
727 | consistency; see ADR-222 for more.. The API is more or less the same;
728 | see their respective godocs for details. The receiver variable should
729 | always be named `suite`, e.g.
730 | ```go
731 | func (suite *mySuite) TestFoo() { … }
732 | ```
733 |
734 | A file may contain a single suite, or multiple suites if additional
735 | grouping is useful. The suite type should come first, followed by the
736 | methods, then the runner-test. If there are multiple suites, they
737 | should come one after the other, and not be interleaved; this makes it
738 | easy to see what code goes with what.
739 | ```go
740 | // good
741 | type mySuite struct{ servicetest.Suite }
742 | // methods of mySuite
743 | // runner-test for mySuite
744 |
745 | type mySuiteWithField struct {
746 | servicetest.Suite
747 | myVar int
748 | }
749 | // methods of mySuiteWithField
750 | // runner-test for mySuiteWithField
751 | ```
752 |
753 | Suites may override testify lifecycle methods; if they do so those
754 | methods must call the "super", i.e. suite.Suite.MethodName().
755 |
756 | ### Generally, use require over assert
757 |
758 | The default style in Testify is for assertions to allow the test to
759 | continue running:
760 | ```go
761 | suite.Assert().Equal(3, 1 + 1) // or assert.Equal
762 | suite.Assert().Equal(4, 2 + 3)
763 | ```
764 | will report two errors in the same test. To exit the test if the
765 | assertion fails, one can use require:
766 | ```go
767 | suite.Require().Equal(3, 1 + 1) // or require.Equal
768 | suite.Require().Equal(4, 2 + 3) // will not run
769 | ```
770 | We use the latter style: it tends to give clearer errors. However, we
771 | allow one exception: within a call to `dev/khantest`'s `suite.All`,
772 | use `Assert()` -- this allows reporting multiple error messages which
773 | can aid in debugging. (See its documentation for details.) Do not
774 | use `suite.Equal`: always use the equivalent and more explicit
775 | `suite.Assert().Equal`. (One exception: `suite.FailNow(message)` is
776 | acceptable.)
777 | ```go
778 | // good
779 | suite.Require().Equal(3, 1 + 2)
780 | suite.All(
781 | suite.Assert().Equal(http.StatusOK, responseCode),
782 | suite.Assert().Equal("pong\n", responseText))
783 | if badStuffHappened {
784 | suite.FailNow("yikes, bad stuff happened!")
785 | }
786 |
787 | // bad
788 | suite.Assert().Equal(3, 1 + 2)
789 | suite.Equal(3, 1 + 2)
790 | suite.All(
791 | suite.Equal(http.StatusOK, responseCode),
792 | suite.Equal("pong\n", responseText))
793 | suite.All(
794 | suite.Require().Equal(http.StatusOK, responseCode),
795 | suite.Require().Equal("pong\n", responseText))
796 | ```
797 |
798 | ### Table-driven tests
799 |
800 | In cases where we have many test cases that all look very similar --
801 | maybe we want to test a simple pure function and we have many
802 | different inputs to test it on -- a table-driven test is often a good
803 | option. Testify's `suite.Run` is useful for this: it makes each
804 | sub-case execute as a separate Go test, so failures will be reported
805 | individually. Make sure to give each one a useful name. For example:
806 | ```go
807 | // good
808 | func (suite *mySuite) TestF() {
809 | tests := []struct{
810 | name string
811 | a, b, c int
812 | }{
813 | {"AddPositiveNumbers", 1, 2, 3},
814 | {"AddZeroToPositiveNumber", 0, 1, 1},
815 | …
816 | }
817 | for _, test := range tests {
818 | test := test
819 | suite.Run(test.name, func() {
820 | suite.Require().Equal(test.c, test.a+test.b)
821 | })
822 | }
823 | }
824 | ```
825 |
826 | Note that `test := test` is necessary to ensure that the variable
827 | captured by the closure is not modified as the for loop proceeds.
828 |
829 | In side-effectful application code, table-driven tests are often more
830 | confusing than helpful. If the test body needs more than one or two
831 | conditionals, it may be clearer to just write out every test case
832 | separately.
833 |
834 | Note that ordinary tests should not use `suite.Run`; the test
835 | method-name should suffice to describe the test. It's only necessary
836 | for table-driven tests where each sub-case needs a name.
837 |
838 | ## Error handling
839 |
840 | > [Khan-specific: this entire section is Khan-specific]
841 |
842 | We have a [special page all about error
843 | handling](https://khanacademy.atlassian.net/wiki/spaces/ENG/pages/150208513/Goliath+Errors+Best+Practices)!
844 | In summary:
845 | * use `pkg/lib/errors` to create errors
846 | * do not include PII in errors
847 | * do not include `Sprintf`ed strings in errors
848 | * choose the error-kind that best describes your error
849 | * always wrap non-Khan errors before returning them
850 | * log at error level if the request failed, and at warn level if we can
851 | do fallback
852 | * when creating an error, either return it or log it; do not do both
853 |
--------------------------------------------------------------------------------