├── python.md
├── assets.md
├── html_and_css.md
├── git.md
├── README.md
└── javascript.md
/python.md:
--------------------------------------------------------------------------------
1 | ## Best Practices for Python
2 |
3 | ### Baseline
4 |
5 | * [PEP8](http://www.python.org/dev/peps/pep-0008/).
6 | * [Django Coding Style](https://docs.djangoproject.com/en/dev/internals/contributing/writing-code/coding-style/).
7 | * **Use single-quotes for strings.**
8 |
9 | ### Libraries
10 |
11 | For consistency, prefer the following libraries to others that perform the same tasks:
12 |
13 | * [Fabric](http://docs.fabfile.org/) for project tasks
14 | * [Flask](http://flask.pocoo.org/) for light web apps **or** [Django](https://www.djangoproject.com/) for heavy web apps
15 | * [boto](https://github.com/boto/boto) for accessing AWS programmatically
16 | * [pytz](http://pytz.sourceforge.net/) for manipulating timezones
17 | * [psycopg2](http://www.initd.org/psycopg/) for accessing Postgres
18 | * [lxml](http://lxml.de/) for XML/DOM manipulation
19 | * [Requests](http://docs.python-requests.org/en/latest/) for working over HTTP
20 |
21 | ### Specifics
22 |
23 | * When testing for nulls, always use ``if foo is None`` rather than ``if !foo`` so ``foo == 0`` does not cause bugs.
24 | * Always initialize and store datetimes in the UTC timezone. Never use naive datetimes for any reason.
25 | * Always use ``with`` when accessing resources that need to be closed.
26 | * Always access blocking resources (files, databases) as little as possible.
27 | * When accessing a dictionary element that may not exist, use ``get()``. For example, ``os.environ.get('DEPLOYMENT_TARGET', None)``.
28 | * Project settings that will be used by both fabric and other code should be isolated in ``app_config.py``. ``fabfile.py`` and Django's ``settings.py`` should import from this file to prevent duplication.
29 | * Imports should be organized into three blocks: stdlib modules, third-party modules and our own modules. Each group should be alphabetized.
30 | * Avoid ``from foo import *``. It is the mindkiller.
31 | * Functions that are de facto "private" (only called within a module or only called within a class) should be prefixed with a single `_`, e.g. `_slugify`.
32 |
33 | ### Flask
34 |
35 | * All views should return with ``make_response``.
36 |
--------------------------------------------------------------------------------
/assets.md:
--------------------------------------------------------------------------------
1 | ## Best Practices for Assets
2 |
3 | * File extensions should always be lowercase.
4 | * When possible, lazy-load assets.
5 | * Files can be checked into source control if they're under 5MB, and don't have a method of getting them through an automated process. Alternatively, provide a build task that will create/download those assets that are larger or depend on data sources (such as the covers for the book concierge).
6 |
7 | ### Audio
8 |
9 | * Music should be encoded as 128bps CBR Stereo:
10 | * MP3: `lame -m s -b 128 input.wav output.mp3`
11 | * Voice should be encoded as 96bps CBR Mono:
12 | * MP3: `lame -m m -b 96 input.wav output.mp3`
13 |
14 | ### Photo
15 |
16 | You can resize images on the command line with ImageMagick. This script (borrowed from the [Ellicott City](https://github.com/nprapps/ellicott-city#snippets) project) will look at all the JPGs in a folder and saved the resized versions in a `resized` folder:
17 |
18 | ```
19 | for f in *.jpg; do magick $f -quality 75 -resize 1600x1200\> -strip -sampling-factor 4:2:0 -define jpeg:dct-method=float -interlace Plane resized/$f; done
20 | ```
21 |
22 | ### Video
23 |
24 | Video encoding depends on the role of the video in a project--ambient video (such as animated backdrops) should be much smaller, because the user doesn't have a choice about playback. On-demand video can be much larger--but consider uploading it to a hosting service like YouTube instead of manually encoding, since those will automatically size and compress for different screen sizes and bandwidth requirements.
25 |
26 | When encoding for a browser, use FFMPEG to output as MP4. The command for doing this in a way that will cooperate with all platforms is somewhat verbose:
27 |
28 | ```sh
29 | ffmpeg \
30 | -i inputfile \
31 | -ss 0 -t 5 \
32 | -an \
33 | -vcodec libx264 \
34 | -preset veryslow \
35 | -strict -2 \
36 | -pix_fmt yuv420p \
37 | -crf 21 \
38 | -vf scale=800:-1 \
39 | outputfile.mp4
40 | ```
41 |
42 | Note that the order of arguments is important. Some of these parameters may be tweakable depending on what your video is meant to accomplish.
43 |
44 | * `-an` - Removes audio for autoplaying video in modern browsers
45 | * `-ss X -t Y` - Extracts Y seconds, starting at X
46 | * `-vf scale=ZZZ:-1` - Resizes the video to ZZZ across, maintaining its current aspect ratio.
47 | * `-crf X` - Sets the quality of the compression to X, where 0 is lossless and 51 is gibberish. 21 is a good starting place.
48 |
49 |
--------------------------------------------------------------------------------
/html_and_css.md:
--------------------------------------------------------------------------------
1 | ## Best Practices for HTML and CSS
2 |
3 |
4 | ### HTML
5 |
6 | * Element IDs and class names should always be `lowercase-with-dashes`.
7 | * Put modals and JST templates in their own files. In the app-template these belong in the `templates` and `jst` directories, respectively. When this isn't feasible, put modals in the page footer first, followed by inline javascript templates.
8 | * Attributes should be double-quoted, even in cases where it will parse without quotes.
9 | * Images should always have an "alt" attribute that contains a screenreader-friendly description of their contents. If the image is only for presentational purposes, set the attribute to an empty string so that it will be ignored.
10 | * Follow [WAI-ARIA best practices](https://www.w3.org/TR/wai-aria-practices/) for interactive elements, such as buttons, menus, or tabs. Test all HTML in a screenreader with this as a guide.
11 | * Use semantic HTML for the appropriate purposes.
12 | - Never use a `
` for a clickable element. If the element updates the page, use a `
`, and if it navigates to another page, use ``.
13 | - When possible, prefer built-in HTML elements (such as ``, ``, or ` `) to custom UI.
14 | - Use ``, ``, ``, ``, and `` to mark up the document outline and provide navigation landmarks. Other HTML tags may be useful [depending on support](http://html5doctor.com/).
15 | * Never set an element's "tabindex" to anything other than "-1" (for elements that are only focused via JavaScript) or "0" (for elements that should be made focusable as normal).
16 |
17 | ### LESS/CSS
18 |
19 | * Prefer classes to IDs: the latter is an order of magnitude higher in specificity when ranking selectors, which makes it difficult to manage style conflicts.
20 | * When managing stateful display styles, start from the ARIA best practices whenever possible. For example, when creating a toggle button, indicate its state based on `[aria-pressed="true"]` instead of adding an "active" class.
21 | * Minimize nesting, but use it to create scoped styles for page components. Excessive nesting makes it difficult to override styles later on. A nested component should have all its base styles at the top, followed by descendant styles. Do not intersperse descendent styles and top-level styles.
22 | * Mobile styles should be nested inside the component they override, as close to the overridden style as possible.
23 | * Use variables whenever possible for colors, media queries, shadows, and other shared visual traits. Consider using [CSS custom properties](https://developer.mozilla.org/en-US/docs/Web/CSS/--*) for any styles that change based on state in multiple places.
24 | * Never override the `:focus` selector. Always provide styles for both `:hover` and `:focus` on any interactive element.
--------------------------------------------------------------------------------
/git.md:
--------------------------------------------------------------------------------
1 | # Best Practices for Git
2 |
3 | Git (through GitHub) is our primary means of collaboration, so it's important that we be able to do so without losing data or time. Unfortunately, Git is also incredibly complex, so this can be difficult to do. The following guidelines attempt to create a workflow that will minimize time spent debugging repositories.
4 |
5 | ## Reading materials
6 |
7 | * [Pro Git](https://git-scm.com/book/en/v2) is available for free and serves as a good explanation of the concepts involved.
8 | * Although we do not use branches heavily, [Git Flow](https://guides.github.com/introduction/flow/) is a good intro to their workflow.
9 |
10 | ## The basics
11 |
12 | A Git repo is a chain of historical snapshots, each of which depends on the previous snapshot. Because there's a strong, cryptographically-signed relationship between entries in the repo history, it's very difficult (if not impossible) to edit history in Git. Our priorities, therefore, are:
13 |
14 | * Never check in any secret values or credentials. When possible, don't even keep them in the same folder as the rest of the code, so that you can't commit them accidentally.
15 | * Try to keep history as a single stream of entries, and minimize the number of merges.
16 |
17 | When working in a repo, you should commit code fairly regularly. To keep from creating unnecessary merge commits, adding code should involve the following steps:
18 |
19 | 1. `git pull --rebase` - Get any commits that may have occurred since the last time you synchronized with the repo. This will also work if you've already committed.
20 | 1. If you have conflicts, use `git stash` to store your local changes, pull again, then `git stash pop` to unshelve your changes back into the folder.
21 | 1. `git status` to check that there aren't any spurious deletions or oddities.
22 | 1. Finally, `git add` and `git commit` to save your changes and `git push` to send them to the remote repo.
23 |
24 | We strongly recommend using a GUI client for GitHub to simplify this process. Using the [GitHub Desktop](https://desktop.github.com) app, for example, you'll always have a diff view of your current changes before committing, which means it'll be obvious if you are accidentally about to erase data. The client takes care of adding staged changes for you, which simplifies commits. It's also simpler to right-click and revert individual files, or create a complete revert commit to undo a bad merge.
25 |
26 | ## Do's and Don'ts
27 |
28 | ### Do...
29 |
30 | * ...always `git status` before committing, to make sure that your staged changes look the way you'd expect, and don't touch other files unintentionally.
31 | * ...set `git config --global pull.rebase true` so that your pulls automatically rebase instead of merging, without needing the command line flag.
32 |
33 | ### Don't...
34 |
35 | * ...use `git add .` or `git commit -a` to add all changed files to the index for committing. It's easy to sweep up changes that you didn't mean to make if your branch is out of sync with the main repo.
36 | * ...merge without looking at the commit message and ensuring that all the files and commits listed are ones that you actually want to change. If there's more than 30 of either, you probably have a problem!
37 | * ...commit credentials, or keep any credentials or secret tokens inside of a repository.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # NPR Visuals' Best Practices
2 |
3 | The contents of this repository are released under a [Creative Commons CC BY 3.0 License](http://creativecommons.org/licenses/by/3.0/deed.en_US).
4 |
5 | ## Index
6 |
7 | * [Project documentation](#project-documentation)
8 | * [Naming things](#naming-things)
9 | * [Version control](#version-control)
10 | * [Servers](#servers)
11 | * [HTML and CSS](#html-and-css)
12 | * [Javascript](#javascript)
13 | * [Python](#python)
14 | * [Assets](#assets)
15 |
16 | ## Project documentation
17 |
18 | Always ensure the following things are documented in the README:
19 |
20 | * Steps to setup the project from a blank slate. (Data loading, etc.)
21 | * Required environment variables. If these are secrets they should also be stored in the team Dropbox.
22 | * Cron jobs that must be installed on the servers. When using the app-template specifying these in the `crontab` file is sufficient.
23 | * Dependencies that are not part of our standard stack. This includes documenting how to install them. Whenever feasible this documentation should be in the form of `fab` commands.
24 |
25 | ## Naming things
26 |
27 | Naming things (variables, files, classes, etc.) consistently and intuitively is one of the hardest problems in computer science. To make it easier, follow these conventions:
28 |
29 | * Always proceed from more general to more specific. For example, ``widget-skinny`` is better than ``skinny-widget``.
30 | * Strive for parallelism. If you have a `begin()` function, then have an `end()` function (not `stop()` or `done()`).
31 | * Group related names with common prefixes, e.g. `search_query` and `search_address`.
32 | * Prefer more specific terms to more vague ones. If it's an address call it `address`, not `location`.
33 | * When a function operates on a variable, their naming should be consistent. If working with `updates` then `process_updates()`, don't `process_changes()`.
34 | * Maintain naming conventions between lists and their iterators: `for update in updates`, not `for record in updates`.
35 |
36 |
37 | Prefer... to...
38 | create insert, add, new
39 | update change, edit
40 | delete remove, purge
41 | setup init
42 | make build, generate
43 | wrapper wrap
44 | render draw
45 |
46 |
47 | (Note: sometimes these words don't mean the same thing, but when they do, prefer the former.)
48 |
49 |
50 | ## Version control
51 |
52 | * Development of major features should happen on separate branches which periodically merge *from* ``master`` until development of the feature is complete.
53 | * **Never, ever store passwords, keys or credentials in any repository.** (Use environment variables instead.)
54 |
55 | See [git.md](https://github.com/nprapps/bestpractices/blob/master/git.md).
56 |
57 | ## Servers
58 |
59 | * Environment variables belong in `/etc/environment`. This file should be sourced by cron jobs. (This happens automatically when using `run_on_server.sh`.)# git
60 |
61 | ## HTML and CSS
62 |
63 | See [html_and_css.md](https://github.com/nprapps/bestpractices/blob/master/html_and_css.md).
64 |
65 | ## Javascript
66 |
67 | See [javascript.md](https://github.com/nprapps/bestpractices/blob/master/javascript.md).
68 |
69 | ## Python
70 |
71 | See [python.md](https://github.com/nprapps/bestpractices/blob/master/python.md).
72 |
73 | ## Assets
74 |
75 | See [assets.md](https://github.com/nprapps/bestpractices/blob/master/assets.md).
76 |
77 |
--------------------------------------------------------------------------------
/javascript.md:
--------------------------------------------------------------------------------
1 | # Best Practices for Javascript
2 |
3 | ## General
4 |
5 | Broadly speaking, the [Google JS style guide](https://google.github.io/styleguide/jsguide.html) is a reasonable starting place, but it's not dogma.
6 |
7 | *Above all, our priority is clarity of intent.* That means naming variables with descriptive, clear names; splitting code into short, reusable modules; and keeping the flow of data clear. By contrast, the kinds of quotes you use, how you put spaces around keywords or objects, or whether you like to declare functions using expressions or variable assignment--none of these things significantly alter the clarity of the code. If there's a style issue that is particularly contentious, we will go with whatever [Prettier](https://prettier.io) emits, and you may want to install its plugin for your editor of choice.
8 |
9 | The TL;DR of our style:
10 |
11 | * Indentation: two spaces, no tabs.
12 | * `camelCase` (not `snake_case`) for variable names, `SHOUTING_SNAKE_CASE` for constants.
13 | * End all statements with a semicolon. Do not use comma-first style in object or array literals.
14 | * Avoid global variables and state whenever possible.
15 | * We support whatever browsers are officially supported for the main site. Currently, this is all modern browsers (IE not included).
16 | * Prefer clear (even overly-verbose) code over "clever" one-liners and obfuscated syntax.
17 |
18 | On the topic of clarity, one place where it is important to be clear is when doing typecasting to and from primitive values. For these, rather than the shorthand operators, it's better to spell out the conversion explicitly.
19 |
20 | ```js
21 | // not this:
22 | var n = +n;
23 | var s = s + "";
24 |
25 | // this:
26 | var n = Number(n);
27 | var s = String(s);
28 |
29 | // use map for bulk conversions
30 | var numericalValues = stringValues.map(Number);
31 | ```
32 |
33 | The one place that we may use the shorthand is when converting to an integer, since there is no explicit integer type in JavaScript. In this case, you may use `var int = n | 0;` to do the conversion (or `Math.floor()`).
34 |
35 | ## ES2015+ syntax
36 |
37 | We prefer to use new syntax where it will add expressiveness, and where it does not incur a size or speed penalty due to transpilation. The [Babel ES2015 guide](https://babeljs.io/docs/en/learn) is helpful for learning these new features, as well as determining when they will create overly-complicated output code.
38 |
39 | ### `let` and `const`
40 |
41 | Use these if you're comfortable with them. `var` remains acceptable, if you understand the quirks around its scoping. All variable should be declared with one of the three--never use the global assignment form, and try not to use global state if possible.
42 |
43 | ### Arrow functions
44 |
45 | Arrow functions should primarily be used in two cases: when we want to preserve the value of `this`, or when the function is a single expression. The latter case is particularly useful in D3, or for `map` and `reduce`. If the body expands to multiple lines, or if you need to return an object, use a regular function expression.
46 |
47 | ```js
48 | // use arrows for short, pure functions
49 | d3Container
50 | .select("rect")
51 | .data(data)
52 | .enter()
53 | .append("circle")
54 | .attr("fill", d => colorScale(d.label))
55 | .attr("x", d => xScale(d.x))
56 | .attr("y", d => yScale(d.y));
57 |
58 | // Also useful for loops
59 | elements.forEach(el => el.style.background = "red");
60 |
61 | // Returning objects with arrows is hard to read:
62 | keys.map(key => ({
63 | key,
64 | value: data[key]
65 | }));
66 |
67 | // use a full function instead
68 | keys.map(function(key) {
69 | var value = data[key];
70 | return { key, value };
71 | });
72 | ```
73 |
74 | ### Destructuring, spread, and object literals
75 |
76 | When possible, use destructuring to pull out properties from an object or array, especially when there are multiple variables being assigned.
77 |
78 | Likewise, when creating objects from existing values, it's preferable to name your variables to match the object and assign them using the literal shorthand (e.g., `var text = "hello, world"; var obj = { text };`).
79 |
80 | Use spread instead of `Function.apply` when calling a function with multiple arguments, as it's more readable and doesn't have concerns about assigning the context object. You can also use the object spread instead of `Object.assign()` for combining objects, as it avoids accidentally mutating the initial merged object. Likewise, use `...rest` for functions that can take variable arguments instead of using the `arguments` object directly.
81 |
82 | ```js
83 | // use destructuring when extracting items from modules or data
84 | var { classify } = require("./lib/helpers");
85 | var { key, value } = item;
86 |
87 | // destructuring is useful for processing dates
88 | var [ m, d, y ] = dateString.split("/").map(Number);
89 |
90 | // Use spread for variadic functions, or combining objects
91 | var min = Math.max(...values);
92 | destinationArray.push(...newItems);
93 |
94 | var d3 = {
95 | ...require("d3-axis"),
96 | ...require("d3-scale")
97 | };
98 | ```
99 |
100 | ### Template strings
101 |
102 | Never concatenate more than two strings together--use a template string instead to do interpolation. However, try to avoid doing large amounts of work inside the template string, and do not use them where a real templating engine would be a better choice (i.e., do not combine template strings with `map()` to build repeated HTML structures, especially if there are conditionals).
103 |
104 | ```js
105 | // too much noise
106 | var translate = "translate(" + margins.left + ", " + margins.top + ")";
107 |
108 | // cleaner
109 | var translate = `translate(${margins.left}, ${margins.top})`;
110 |
111 | // cleanest
112 | var makeTranslate = (x, y) => `translate(${x}, ${y})`;
113 | var translate = makeTranslate(margins.left, margins.top);
114 |
115 | ```
116 |
117 | ### Async/await and generators
118 |
119 | We have recently started using `async`/`await`, particularly in combination with `fetch`. These keywords essentially "unwrap" a promise so that your code can be written in execution order. Whenever possible, use this to make your code flow more clear. However, be careful of cases where these keywords will cause your code to behave sequentially if parallel behavior would be more effective.
120 |
121 | ```js
122 | // unintended sequential downloads
123 | var first = await fetch("first.json");
124 | var second = await fetch("second.json");
125 |
126 | // use destructuring and Promise.all to parallelize
127 | var [first, second] = await Promise.all([
128 | fetch("first.json"),
129 | fetch("second.json")
130 | ]);
131 | ```
132 |
133 | In the case of the fetch API and other native async functions, the API may involve several awaited steps. In this case, it's better to either break this out into several steps, or use a small `then()` function to encapsulate the intermediate steps. Do not abuse parentheses to create `await` one-liners.
134 |
135 | ```js
136 | // this is hard to read/reason about
137 | var data = await (await fetch("json")).json();
138 |
139 | // this is easier to read
140 | var response = await fetch("json");
141 | var data = await response.json();
142 |
143 | // a reasonable compromise, using the actual promise
144 | var data = await fetch("json").then(r => r.json());
145 | ```
146 |
147 | ### Modules
148 |
149 | You can use ES modules in your code if you want--Babel will transpile it into `require()` calls for the final output. You can also use CommonJS exports. Your modules should export either an object or a function, with the former preferred so that you can pull out specific parts of the module using destructuring.
150 |
151 | ### Classes/prototypes
152 |
153 | ES classes are supported by all browsers that we currently target, and they may be used if you are more comfortable with their code pattern than with prototypal inheritance. It's best not to build deep inheritance structures in JS.
154 |
155 | In both classes and prototypes, only initialize properties that are reference values (objects and arrays) in the constructor function. You should not be creating methods in the constructor, as this wastes memory. Put those on the prototype or declare them in the class itself. You may, however, use the constructor to bind methods to this particular instance, if necessary.
156 |
157 | ## Libraries and patterns
158 |
159 | Whenever possible, prefer using browser built-ins over any library. They're lighter in weight, are less likely to break, and teach more reusable patterns for novice developers. We have a collection of micro-modules for common tasks that are bigger than one-liners, [here](https://github.com/nprapps/interactive-template/tree/master/root/src/js/lib), such as performing XHR downloads or delegating events.
160 |
161 | That said, we will use libraries or frameworks where their utility is sufficiently high to overcome their bundled size. We'll decide this on a case by case basis.
162 |
163 | ### Document manipulation (vs. jQuery/D3)
164 |
165 | ```js
166 |
167 | // Finding elements
168 | var $ = (s, d = document) => Array.from(d.querySelectorAll(s));
169 | $.one = (s, d = document) => d.querySelector(s);
170 |
171 | // Updating classes or attributes
172 | $(".add-a-class").forEach(el => el.classList.add("selected"));
173 | $(".set-an-attribute").forEach(el => el.setAttribute("fill", "red"));
174 |
175 | // Adding event listeners
176 | var onClick = function() { ... };
177 | $(".clickable").forEach(el => el.addEventListener("click", onClick));
178 |
179 | // Getting data attributes
180 | var dataElement = $.one(".has-data");
181 | var { text } = dataElement.dataset;
182 |
183 | // Simple content setting
184 | var name = "World";
185 | $.one(".status").innerHTML = `Hello, ${name}`;
186 |
187 | // Running code based on window size
188 | var isMobile = window.matchMedia("(max-width: 500px)");
189 | // matches property is "live", will update after resize
190 | if (isMobile.matches) {
191 | // run on a phone
192 | }
193 |
194 | ```
195 |
196 | ### Data collections (vs. Underscore/D3-array)
197 |
198 | ```js
199 | // each
200 | data.forEach((d, i) => doSomething(d, "argument"));
201 |
202 | // pluck()
203 | var plucked = data.map(d => d.property);
204 |
205 | // unique
206 | var unique = [...new Set(data)];
207 |
208 | // filter
209 | var filtered = data.filter(d => d.value == "xyz");
210 |
211 | // min/max/extent
212 | var values = data.forEach(d => d.value);
213 | var min = Math.min(...values);
214 | var max = Math.max(...values);
215 | var sorted = values.slice().sort();
216 | var extent = [sorted.shift(), sorted.pop()];
217 |
218 | // Object keys
219 | Object.keys(data).forEach(key => console.log(key));
220 |
221 | // Un-pivoting columnar data
222 | var skipLabels = ["label", "name", "etc"];
223 | DATA.forEach(function(d) {
224 | d.values = Object.keys(d)
225 | .filter(key => skipLabels.indexOf(key) == -1)
226 | .map(function(key) {
227 | return {
228 | key,
229 | value: data[k]
230 | }
231 | });
232 | });
233 |
234 | ```
235 |
--------------------------------------------------------------------------------