feedjira 2.1.0 → 2.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.rubocop.yml +9 -2
  4. data/CHANGELOG.md +4 -0
  5. data/LICENSE +1 -1
  6. data/README.md +210 -7
  7. data/Rakefile +5 -0
  8. data/feedjira.gemspec +2 -1
  9. data/lib/feedjira.rb +7 -1
  10. data/lib/feedjira/configuration.rb +76 -0
  11. data/lib/feedjira/core_ext/date.rb +1 -0
  12. data/lib/feedjira/core_ext/string.rb +1 -0
  13. data/lib/feedjira/core_ext/time.rb +5 -1
  14. data/lib/feedjira/date_time_utilities.rb +11 -3
  15. data/lib/feedjira/date_time_utilities/date_time_epoch_parser.rb +13 -0
  16. data/lib/feedjira/date_time_utilities/date_time_language_parser.rb +2 -0
  17. data/lib/feedjira/date_time_utilities/date_time_pattern_parser.rb +6 -1
  18. data/lib/feedjira/feed.rb +87 -69
  19. data/lib/feedjira/feed_entry_utilities.rb +5 -2
  20. data/lib/feedjira/feed_utilities.rb +11 -1
  21. data/lib/feedjira/parser.rb +1 -1
  22. data/lib/feedjira/parser/atom.rb +1 -0
  23. data/lib/feedjira/parser/atom_entry.rb +1 -0
  24. data/lib/feedjira/parser/atom_feed_burner.rb +19 -2
  25. data/lib/feedjira/parser/atom_feed_burner_entry.rb +1 -0
  26. data/lib/feedjira/parser/atom_youtube.rb +1 -0
  27. data/lib/feedjira/parser/atom_youtube_entry.rb +1 -0
  28. data/lib/feedjira/parser/google_docs_atom.rb +2 -1
  29. data/lib/feedjira/parser/google_docs_atom_entry.rb +2 -0
  30. data/lib/feedjira/parser/itunes_rss.rb +1 -0
  31. data/lib/feedjira/parser/itunes_rss_category.rb +1 -0
  32. data/lib/feedjira/parser/itunes_rss_owner.rb +1 -0
  33. data/lib/feedjira/parser/podlove_chapter.rb +2 -0
  34. data/lib/feedjira/parser/rss.rb +1 -0
  35. data/lib/feedjira/parser/rss_feed_burner.rb +1 -0
  36. data/lib/feedjira/parser/rss_feed_burner_entry.rb +1 -0
  37. data/lib/feedjira/preprocessor.rb +2 -0
  38. data/lib/feedjira/version.rb +1 -1
  39. data/spec/feedjira/configuration_spec.rb +25 -0
  40. data/spec/feedjira/date_time_utilities_spec.rb +6 -0
  41. data/spec/feedjira/feed_spec.rb +20 -2
  42. data/spec/feedjira/feed_utilities_spec.rb +18 -0
  43. data/spec/feedjira/parser/atom_feed_burner_spec.rb +32 -1
  44. data/spec/sample_feeds.rb +1 -0
  45. data/spec/sample_feeds/GiantRobotsSmashingIntoOtherGiantRobots.xml +682 -0
  46. metadata +49 -29
@@ -1,3 +1,5 @@
1
+ # rubocop:disable Style/Documentation
2
+ # rubocop:disable Style/DocumentationMethod
1
3
  module Feedjira
2
4
  class Preprocessor
3
5
  def initialize(xml)
@@ -1,3 +1,3 @@
1
1
  module Feedjira
2
- VERSION = '2.1.0'.freeze
2
+ VERSION = '2.1.1'.freeze
3
3
  end
@@ -0,0 +1,25 @@
1
+ require 'spec_helper'
2
+
3
+ describe Feedjira::Configuration do
4
+ describe '.configure' do
5
+ it 'sets follow_redirect_limit config' do
6
+ Feedjira.configure { |config| config.follow_redirect_limit = 10 }
7
+ expect(Feedjira.follow_redirect_limit).to eq(10)
8
+ end
9
+
10
+ it 'sets request_timeout config' do
11
+ Feedjira.configure { |config| config.request_timeout = 45 }
12
+ expect(Feedjira.request_timeout).to eq(45)
13
+ end
14
+
15
+ it 'sets strip_whitespace config' do
16
+ Feedjira.configure { |config| config.strip_whitespace = true }
17
+ expect(Feedjira.strip_whitespace).to be true
18
+ end
19
+
20
+ it 'sets user_agent config' do
21
+ Feedjira.configure { |config| config.user_agent = 'Test User Agent' }
22
+ expect(Feedjira.user_agent).to eq('Test User Agent')
23
+ end
24
+ end
25
+ end
@@ -37,5 +37,11 @@ describe Feedjira::FeedUtilities do
37
37
  expect(time.class).to eq Time
38
38
  expect(time).to eq Time.parse_safely('Wed Aug 31 14:37:00 UTC 2016')
39
39
  end
40
+
41
+ it 'should parse epoch into Time' do
42
+ time = @klass.new.parse_datetime('1472654220')
43
+ expect(time.class).to eq Time
44
+ expect(time).to eq Time.parse_safely('Wed Aug 31 14:37:00 UTC 2016')
45
+ end
40
46
  end
41
47
  end
@@ -224,8 +224,26 @@ describe Feedjira::Feed do
224
224
  parser = Feedjira::Feed.determine_feed_parser_for_xml xml
225
225
  expect(parser).to eq new_parser
226
226
 
227
- # this is a hack so that this doesn't break the rest of the tests
228
- Feedjira::Feed.feed_classes.reject! { |o| o == new_parser }
227
+ Feedjira::Feed.reset_parsers!
228
+ end
229
+ end
230
+
231
+ describe 'when parsers are configured' do
232
+ it 'does not use default parsers' do
233
+ xml = 'Atom asdf'
234
+ new_parser = Class.new do
235
+ def self.able_to_parse?(_)
236
+ true
237
+ end
238
+ end
239
+
240
+ Feedjira.configure { |config| config.parsers = [new_parser] }
241
+
242
+ parser = Feedjira::Feed.determine_feed_parser_for_xml(xml)
243
+ expect(parser).to eq(new_parser)
244
+
245
+ Feedjira.reset_configuration!
246
+ Feedjira::Feed.reset_parsers!
229
247
  end
230
248
  end
231
249
  end
@@ -26,6 +26,24 @@ describe Feedjira::FeedUtilities do
26
26
  end
27
27
  end
28
28
 
29
+ describe 'strip whitespace' do
30
+ context 'strip_whitespace config is true' do
31
+ it 'strips all XML whitespace' do
32
+ Feedjira.configure { |config| config.strip_whitespace = true }
33
+
34
+ expect(@klass.strip_whitespace("\nfoobar\n")).to eq('foobar')
35
+
36
+ Feedjira.configure { |config| config.strip_whitespace = false }
37
+ end
38
+ end
39
+
40
+ context 'strip_whitespace config is false' do
41
+ it 'lstrips XML whitespace' do
42
+ expect(@klass.strip_whitespace("\nfoobar\n")).to eq("foobar\n")
43
+ end
44
+ end
45
+ end
46
+
29
47
  describe 'instance methods' do
30
48
  it 'should provide an updated? accessor' do
31
49
  feed = @klass.new
@@ -19,7 +19,7 @@ module Feedjira::Parser
19
19
  end
20
20
  end
21
21
 
22
- describe 'parsing' do
22
+ describe 'parsing old style feeds' do
23
23
  before(:each) do
24
24
  @feed = AtomFeedBurner.parse(sample_feedburner_atom_feed)
25
25
  end
@@ -56,6 +56,37 @@ module Feedjira::Parser
56
56
  end
57
57
  end
58
58
 
59
+ describe 'parsing alternate style feeds' do
60
+ before(:each) do
61
+ @feed = AtomFeedBurner.parse(sample_feedburner_atom_feed_alternate)
62
+ end
63
+
64
+ it 'should parse the title' do
65
+ expect(@feed.title).to eq 'Giant Robots Smashing Into Other Giant Robots'
66
+ end
67
+
68
+ it 'should parse the description' do
69
+ description = 'Written by thoughtbot'
70
+ expect(@feed.description).to eq description
71
+ end
72
+
73
+ it 'should parse the url' do
74
+ expect(@feed.url).to eq 'https://robots.thoughtbot.com'
75
+ end
76
+
77
+ it 'should parse the feed_url' do
78
+ expect(@feed.feed_url).to eq 'http://feeds.feedburner.com/GiantRobotsSmashingIntoOtherGiantRobots'
79
+ end
80
+
81
+ it 'should parse hub urls' do
82
+ expect(@feed.hubs.count).to eq 1
83
+ end
84
+
85
+ it 'should parse entries' do
86
+ expect(@feed.entries.size).to eq 3
87
+ end
88
+ end
89
+
59
90
  describe 'preprocessing' do
60
91
  it 'retains markup in xhtml content' do
61
92
  AtomFeedBurner.preprocess_xml = true
data/spec/sample_feeds.rb CHANGED
@@ -19,6 +19,7 @@ module SampleFeeds
19
19
  sample_rss_feed: 'TenderLovemaking.xml',
20
20
  sample_rss_entry_content: 'TenderLovemakingFirstEntry.xml',
21
21
  sample_feedburner_atom_feed: 'PaulDixExplainsNothing.xml',
22
+ sample_feedburner_atom_feed_alternate: 'GiantRobotsSmashingIntoOtherGiantRobots.xml',
22
23
  sample_feedburner_atom_entry_content: 'PaulDixExplainsNothingFirstEntryContent.xml',
23
24
  sample_wfw_feed: 'PaulDixExplainsNothingWFW.xml',
24
25
  sample_google_docs_list_feed: 'GoogleDocsList.xml',
@@ -0,0 +1,682 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <?xml-stylesheet type="text/xsl" media="screen" href="/~d/styles/atom10full.xsl"?><?xml-stylesheet type="text/css" media="screen" href="http://feeds.feedburner.com/~d/styles/itemcontent.css"?><feed xmlns="http://www.w3.org/2005/Atom" xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0">
3
+ <title>Giant Robots Smashing Into Other Giant Robots</title>
4
+ <subtitle>Written by thoughtbot</subtitle>
5
+ <id>https://robots.thoughtbot.com/</id>
6
+ <link href="https://robots.thoughtbot.com" />
7
+
8
+ <updated>2016-12-21T00:00:00+00:00</updated>
9
+ <author>
10
+ <name>thoughtbot</name>
11
+ </author>
12
+ <atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="self" type="application/atom+xml" href="http://feeds.feedburner.com/GiantRobotsSmashingIntoOtherGiantRobots" /><feedburner:info uri="giantrobotssmashingintoothergiantrobots" /><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="hub" href="http://pubsubhubbub.appspot.com/" /><feedburner:emailServiceId>GiantRobotsSmashingIntoOtherGiantRobots</feedburner:emailServiceId><feedburner:feedburnerHostname>https://feedburner.google.com</feedburner:feedburnerHostname><feedburner:browserFriendly>This is an XML content feed. It is intended to be viewed in a newsreader or syndicated to another site, subject to copyright and fair use.</feedburner:browserFriendly><entry>
13
+ <title>"Tell, Don't Ask" in Elixir: A Story of Pattern-Matching
14
+ </title>
15
+ <link rel="alternate" href="http://feedproxy.google.com/~r/GiantRobotsSmashingIntoOtherGiantRobots/~3/DeWX2QCae6A/tell-don-t-ask-in-elixir" />
16
+ <author>
17
+ <name>Josh Clayton</name>
18
+ </author>
19
+ <id>https://robots.thoughtbot.com/tell-don-t-ask-in-elixir</id>
20
+ <published>2016-12-21T00:00:00+00:00</published>
21
+ <updated>2016-12-20T22:08:11Z</updated>
22
+ <content type="html">&lt;p&gt;&lt;a href="https://robots.thoughtbot.com/tell-dont-ask"&gt;&amp;ldquo;Tell, Don&amp;rsquo;t Ask&amp;rdquo;&lt;/a&gt; is a &lt;a href="http://martinfowler.com/bliki/TellDontAsk.html"&gt;well-covered&lt;/a&gt; &lt;a href="http://c2.com/cgi/wiki?TellDontAsk"&gt;topic&lt;/a&gt; within
23
+ object-oriented programming communities. Its goal? Encourage encapsulation by
24
+ having the caller &lt;strong&gt;tell&lt;/strong&gt; an object to do something instead of checking on
25
+ state and acting upon it. Almost at odds with the &lt;a href="https://github.com/troessner/reek/blob/master/docs/Control-Couple.md"&gt;control couple&lt;/a&gt; code smell,
26
+ our goal is to have the caller issue explicit commands without concerning
27
+ itself with object state.&lt;/p&gt;
28
+ &lt;h2 id="quottell-don39t-askquot-in-elixir"&gt;
29
+ &lt;a href="#quottell-don39t-askquot-in-elixir"&gt;
30
+ &amp;ldquo;Tell, Don&amp;rsquo;t Ask&amp;rdquo; in Elixir
31
+ &lt;/a&gt;
32
+ &lt;/h2&gt;
33
+
34
+ &lt;p&gt;&lt;a href="http://tech.noredink.com/post/142689001488/the-most-object-oriented-language"&gt;Is Elixir object-oriented?&lt;/a&gt; From a paradigm perspective, Elixir is a
35
+ functional language when looking at aspects like immutability,
36
+ pattern-matching, and functions with inputs and outputs, focused on the sending
37
+ of messages to &amp;ldquo;objects&amp;rdquo; directly. How does &amp;ldquo;Tell, Don&amp;rsquo;t Ask&amp;rdquo; translate?&lt;/p&gt;
38
+
39
+ &lt;p&gt;Thinking about the goal, let&amp;rsquo;s do some mental mapping. In OOP, objects are a
40
+ blueprint with information containing behavior (methods) and data (state).
41
+ In FP, we have functions organized within modules, with state being captured in
42
+ various values (e.g. Elixir&amp;rsquo;s Maps or Structs). We want to avoid having the
43
+ caller (a function) dictate paths based on information present in our data.&lt;/p&gt;
44
+
45
+ &lt;p&gt;Let&amp;rsquo;s write out some non-idiomatic Elixir and see what we can improve.&lt;/p&gt;
46
+
47
+ &lt;pre&gt;&lt;code class="elixir"&gt;defmodule Game.Lobby do
48
+ def add_player(%{game: game} = lobby, player) do
49
+ new_player = cond do
50
+ is_binary(player) -&amp;gt;
51
+ %Game.Player{name: player, id: Game.Player.generate_id}
52
+ is_map(player) -&amp;gt;
53
+ %Game.Player{} |&amp;gt; Map.merge(player)
54
+ true -&amp;gt;
55
+ %Game.Player{}
56
+ end
57
+
58
+ %{lobby |
59
+ game: %{game | players: game.players ++ [new_player]}}
60
+ end
61
+ end
62
+
63
+ defmodule Game.Player do
64
+ defstruct id: 0, name: &amp;quot;New player&amp;quot;, active: true
65
+
66
+ def generate_id do
67
+ UUID.uuid4()
68
+ end
69
+ end
70
+ &lt;/code&gt;&lt;/pre&gt;
71
+
72
+ &lt;p&gt;&lt;code&gt;Game.Lobby.add_player/2&lt;/code&gt; doesn&amp;rsquo;t feel right. There&amp;rsquo;s a significant amount of
73
+ &lt;a href="http://c2.com/cgi/wiki?FeatureEnvySmell"&gt;feature envy&lt;/a&gt; as it cares about the various shapes of &lt;code&gt;player&lt;/code&gt; and how to
74
+ construct a &lt;code&gt;%Game.Player{}&lt;/code&gt;. Also, why is &lt;code&gt;Game.Player.generate_id/0&lt;/code&gt; public?
75
+ It seems all &lt;code&gt;Game.Lobby.add_player/2&lt;/code&gt; should care about is managing its own
76
+ structure (the final two lines of the function).&lt;/p&gt;
77
+
78
+ &lt;p&gt;Instead of having &lt;code&gt;Game.Lobby.add_player/2&lt;/code&gt; care about constructing players,
79
+ generating ids, and so on, let&amp;rsquo;s &lt;strong&gt;tell&lt;/strong&gt; &lt;code&gt;Game.Player&lt;/code&gt; to handle that instead:&lt;/p&gt;
80
+
81
+ &lt;pre&gt;&lt;code class="elixir"&gt;defmodule Game.Lobby do
82
+ def add_player(%{game: game} = lobby, player) do
83
+ %{lobby |
84
+ game: %{game | players: game.players ++ [Game.Player.new(player)]}}
85
+ end
86
+ end
87
+
88
+ defmodule Game.Player do
89
+ defstruct id: 0, name: &amp;quot;New player&amp;quot;, active: true
90
+
91
+ def new(name) when is_binary(name), do: new(%{name: name, id: generate_id})
92
+ def new(a) when is_map(a), do: %__MODULE__{} |&amp;gt; Map.merge(a)
93
+ def new(_), do: %__MODULE__{}
94
+
95
+ defp generate_id, do: UUID.uuid4()
96
+ end
97
+ &lt;/code&gt;&lt;/pre&gt;
98
+
99
+ &lt;p&gt;Here, we move player generation to the &lt;code&gt;Game.Player&lt;/code&gt; module, where it can
100
+ determine how best to generate the struct instead of &lt;code&gt;Game.Lobby.add_player/2&lt;/code&gt;.&lt;/p&gt;
101
+ &lt;h2 id="write-declarative-not-imperative-code"&gt;
102
+ &lt;a href="#write-declarative-not-imperative-code"&gt;
103
+ Write declarative (not imperative) code
104
+ &lt;/a&gt;
105
+ &lt;/h2&gt;
106
+
107
+ &lt;p&gt;By moving player creation logic from &lt;code&gt;Game.Lobby.add_player/2&lt;/code&gt; to
108
+ &lt;code&gt;Game.Player.new/1&lt;/code&gt;, we were able to call a single function to take the
109
+ appropriate action based on data. It is important to note that the data it&amp;rsquo;s
110
+ acting upon specifically is behavior to construct a &lt;code&gt;%Game.Player{}&lt;/code&gt;.&lt;/p&gt;
111
+
112
+ &lt;p&gt;This becomes more important when using the &lt;a href="http://elixir-lang.org/docs/stable/elixir/Kernel.html#%7C%3E/2"&gt;pipe operator&lt;/a&gt;, which shines as a
113
+ way to transform data.&lt;/p&gt;
114
+
115
+ &lt;p&gt;&amp;ldquo;Tell, don&amp;rsquo;t ask&amp;rdquo; is a way to encourage developers to &lt;a href="https://medium.freecodecamp.com/imperative-vs-declarative-programming-283e96bf8aea"&gt;write declarative code
116
+ instead of imperative code&lt;/a&gt;. Imperative code asks questions before making
117
+ decisions; declarative code issues a command and expects it to be done
118
+ correctly.&lt;/p&gt;
119
+ </content>
120
+ <summary>Write declarative Elixir code by applying &amp;ldquo;Tell, Don&amp;rsquo;t Ask&amp;rdquo;.</summary>
121
+ <feedburner:origLink>https://robots.thoughtbot.com/tell-don-t-ask-in-elixir</feedburner:origLink></entry>
122
+ <entry>
123
+ <title>Avoiding Out of Memory Crashes on Mobile</title>
124
+ <link rel="alternate" href="http://feedproxy.google.com/~r/GiantRobotsSmashingIntoOtherGiantRobots/~3/Px1_JC1CMVg/avoiding-out-of-memory-crashes-on-mobile" />
125
+ <author>
126
+ <name>Steff Kelsey</name>
127
+ </author>
128
+ <id>https://robots.thoughtbot.com/avoiding-out-of-memory-crashes-on-mobile</id>
129
+ <published>2016-12-20T00:00:00+00:00</published>
130
+ <updated>2016-12-19T19:30:45Z</updated>
131
+ <content type="html">&lt;p&gt;One reason why it is difficult to develop software for mobile devices is that
132
+ the hardware is not the best compared to deploying to a console or a &amp;ldquo;real&amp;rdquo;
133
+ computer. Resources are limited. One particularly sparse resource is RAM. Out
134
+ of memory exceptions are common on both Android and iOS if you&amp;rsquo;re dealing with
135
+ large files. Recently, when building a Google VR 360 video player, we went over
136
+ the 1GB of RAM available on older iOS devices pretty quickly.&lt;/p&gt;
137
+ &lt;h2 id="what-not-to-do"&gt;
138
+ &lt;a href="#what-not-to-do"&gt;
139
+ What Not to Do
140
+ &lt;/a&gt;
141
+ &lt;/h2&gt;
142
+
143
+ &lt;p&gt;One of my big complaints about the Unity manual and many tutorials is they
144
+ usually just show you how to do something really quickly and don&amp;rsquo;t always tell
145
+ you the exact use case or how it can just flat out fail. For example, using the
146
+ relatively new &lt;code&gt;UnityWebRequest&lt;/code&gt;, you can download a file over HTTP like this:&lt;/p&gt;
147
+
148
+ &lt;pre&gt;&lt;code class="csharp"&gt;private IEnumerator loadAsset(string path)
149
+ {
150
+ using (UnityWebRequest webRequest = new UnityWebRequest(path))
151
+ {
152
+ webRequest.downloadHandler = new DownloadHandlerBuffer();
153
+ webRequest.Send();
154
+ while (!webRequest.isDone)
155
+ {
156
+ yield return null;
157
+ }
158
+ if (string.IsNullOrEmpty(webRequest.error))
159
+ {
160
+ FileComplete(this, new FileLoaderCompleteEventArgs(
161
+ webRequest.downloadHandler.data));
162
+ }
163
+ else
164
+ {
165
+ Debug.Log(&amp;quot;error! message: &amp;quot; + webRequest.error);
166
+ }
167
+ }
168
+ }
169
+ &lt;/code&gt;&lt;/pre&gt;
170
+
171
+ &lt;p&gt;These are all off the shelf parts from Unity, with the exception of the
172
+ &lt;code&gt;FileLoaderCompleteEventArg&lt;/code&gt; but just assume that we use that to pass off the
173
+ downloaded bytes as an array eg: &lt;code&gt;byte[]&lt;/code&gt;. Notice this returns an &lt;code&gt;IEnumerator&lt;/code&gt;
174
+ and utilizes &lt;code&gt;yield&lt;/code&gt; statements so it should be run in a &lt;code&gt;Coroutine&lt;/code&gt;. What
175
+ happens here is that the &lt;code&gt;UnityWebRequest&lt;/code&gt; will open up a connection to the
176
+ given path, download everything into a byte array contained within the
177
+ &lt;code&gt;DownloadHandlerBuffer&lt;/code&gt;. The &lt;code&gt;FileComplete&lt;/code&gt; event will fire if there are no
178
+ errors, sending the entire byte array to the subscribing class. Easy, right? For
179
+ small files, sure. But we were making a 360 Video player. Our max resolution was
180
+ 1440p. The first sample files we got for testing were bigger than 400MB. The
181
+ iPhone 7, with 2GB of RAM, took it like a champ. The iPhone 6, with 1GB of RAM,
182
+ crashed like a piano dropped from a helicopter.&lt;/p&gt;
183
+ &lt;h2 id="why-did-my-app-just-crash"&gt;
184
+ &lt;a href="#why-did-my-app-just-crash"&gt;
185
+ Why Did my App Just Crash?
186
+ &lt;/a&gt;
187
+ &lt;/h2&gt;
188
+
189
+ &lt;p&gt;Let&amp;rsquo;s look at the guts of these components. The main focus is on the
190
+ &lt;code&gt;DownloadHandlerBuffer&lt;/code&gt; object. When it is first created, it will start by
191
+ preallocating memory for a small byte array where it will store all the
192
+ downloaded bytes. As the bytes come in, it will periodically expand the size of
193
+ the array. In our test case, it was expanding the array until it could hold
194
+ 400MB. And because each additional allocation is a guess, it will most likely
195
+ overshot that amount. Note, I am speculating here because I have not looked at
196
+ the source code for the &lt;code&gt;DownloadBufferHandler&lt;/code&gt;. There is a chance it allocates
197
+ space based on the Content-Length header returned with the HTTP Response. But,
198
+ the result is the same; it will use up at least 400MB of RAM. That&amp;rsquo;s 40% of the
199
+ 1GB that the iPhone 6 has! We&amp;rsquo;re already in dangerous territory. I know what
200
+ you&amp;rsquo;re saying, &amp;ldquo;Steff, why did it crash if we only used 40% of the RAM?&amp;rdquo; There
201
+ are two ways to find the answer. One (and give Unity credit here) is in the
202
+ documentation for &lt;a href="https://docs.unity3d.com/ScriptReference/Networking.DownloadHandlerBuffer.html"&gt;&lt;code&gt;DownloadHandlerBuffer&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
203
+
204
+ &lt;blockquote&gt;
205
+ &lt;p&gt;Note: When accessing DownloadHandler.data or DownloadHandler.text on this
206
+ subclass, a new byte array or string will be allocated each time the property
207
+ is accessed.&lt;/p&gt;
208
+ &lt;/blockquote&gt;
209
+
210
+ &lt;p&gt;So, by accessing the data property, Unity allocates an &lt;em&gt;additional 400MB of
211
+ memory&lt;/em&gt; to pass off the byte array into the EventArg. Now we have used 800MB of
212
+ RAM just on handling this one file. The OS has other services running plus you
213
+ very likely have RAM allocated for bitmaps and UI and logic. You&amp;rsquo;re doomed!&lt;/p&gt;
214
+ &lt;h2 id="profiling-memory-allocations"&gt;
215
+ &lt;a href="#profiling-memory-allocations"&gt;
216
+ Profiling Memory Allocations
217
+ &lt;/a&gt;
218
+ &lt;/h2&gt;
219
+
220
+ &lt;p&gt;If you didn&amp;rsquo;t read the docs, and they&amp;rsquo;re long: I get it, you could have found
221
+ this memory leak by running the application in Unity while using the Profiler
222
+ &lt;em&gt;AND&lt;/em&gt; by running the application on an iOS device while using a valuable free
223
+ tool from Apple: Instruments. The Allocations instrument captures information
224
+ about memory allocation for an application. I recommend using the Unity Profiler
225
+ heavily for testing in the Editor and then continuing performance testing on
226
+ device for each platform. They all act differently. Using the Profiler in the
227
+ Editor is only your first line of defense. In this case I only properly
228
+ understood what was happening when I watched it unfold in a recording using the
229
+ Allocations instrument.&lt;/p&gt;
230
+ &lt;h2 id="streams-to-the-rescue"&gt;
231
+ &lt;a href="#streams-to-the-rescue"&gt;
232
+ Streams to the Rescue
233
+ &lt;/a&gt;
234
+ &lt;/h2&gt;
235
+
236
+ &lt;p&gt;There is a way to download large files and save them without using unnecessary
237
+ RAM. &lt;em&gt;Streams!&lt;/em&gt; Since we plan on immediately saving these large video files
238
+ in local storage on device to be ready for offline viewing, we need to send the
239
+ downloaded bytes right into a File as they are received. When doing that, we can
240
+ reuse the same byte array and never have to allocate more space. Unity outlines
241
+ how to do that &lt;a href="https://docs.unity3d.com/Manual/UnityWebRequest-CreatingDownloadHandlers.html"&gt;here&lt;/a&gt;, but below is an expanded example that includes a &lt;code&gt;FileStream&lt;/code&gt;:&lt;/p&gt;
242
+
243
+ &lt;pre&gt;&lt;code class="csharp"&gt;public class ToFileDownloadHandler : DownloadHandlerScript
244
+ {
245
+ private int expected = -1;
246
+ private int received = 0;
247
+ private string filepath;
248
+ private FileStream fileStream;
249
+ private bool canceled = false;
250
+
251
+ public ToFileDownloadHandler(byte[] buffer, string filepath)
252
+ : base(buffer)
253
+ {
254
+ this.filepath = filepath;
255
+ fileStream = new FileStream(filepath, FileMode.Create, FileAccess.Write);
256
+ }
257
+
258
+ protected override byte[] GetData() { return null; }
259
+
260
+ protected override bool ReceiveData(byte[] data, int dataLength)
261
+ {
262
+ if (data == null || data.Length &amp;lt; 1)
263
+ {
264
+ return false;
265
+ }
266
+ received += dataLength;
267
+ if (!canceled) fileStream.Write(data, 0, dataLength);
268
+ return true;
269
+ }
270
+
271
+ protected override float GetProgress()
272
+ {
273
+ if (expected &amp;lt; 0) return 0;
274
+ return (float)received / expected;
275
+ }
276
+
277
+ protected override void CompleteContent()
278
+ {
279
+ fileStream.Close();
280
+ }
281
+
282
+ protected override void ReceiveContentLength(int contentLength)
283
+ {
284
+ expected = contentLength;
285
+ }
286
+
287
+ public void Cancel()
288
+ {
289
+ canceled = true;
290
+ fileStream.Close();
291
+ File.Delete(filepath);
292
+ }
293
+ }
294
+ &lt;/code&gt;&lt;/pre&gt;
295
+
296
+ &lt;p&gt;And to use the above in our coroutine:&lt;/p&gt;
297
+
298
+ &lt;pre&gt;&lt;code class="csharp"&gt;private IEnumerator loadAsset(string path, string savePath)
299
+ {
300
+ using (UnityWebRequest webRequest = new UnityWebRequest(path))
301
+ {
302
+ webRequest.downloadHandler = new ToFileDownloadHandler(new byte[64 * 1024],
303
+ savePath);
304
+ webRequest.Send();
305
+ ...
306
+ ...
307
+ }
308
+ }
309
+ &lt;/code&gt;&lt;/pre&gt;
310
+
311
+ &lt;p&gt;Looking first at our new &lt;code&gt;ToFileDownloadHandler&lt;/code&gt;, we extended Unity&amp;rsquo;s
312
+ &lt;code&gt;DownloadHandlerScript&lt;/code&gt; and have overridden the required methods. The magic
313
+ happens in two places. First, we pass in a byte array to the base class via the
314
+ constructor. This let&amp;rsquo;s Unity know that we want to re-use that byte array on
315
+ each &lt;code&gt;ReceiveData&lt;/code&gt; callback where we only allocate a small amount of RAM once.
316
+ Second, we use a &lt;code&gt;FileStream&lt;/code&gt; object to write the bytes directly to our desired
317
+ file. The rest of the code is there to handle canceling the request. Whenever
318
+ you deal with &lt;code&gt;FileStream&lt;/code&gt; objects, you &lt;em&gt;must&lt;/em&gt; remember to close them out when
319
+ you&amp;rsquo;re done.&lt;/p&gt;
320
+
321
+ &lt;p&gt;Looking at the &lt;code&gt;loadAsset&lt;/code&gt; method, we added a parameter for the path to where
322
+ the file will be saved locally and we defined the size of the buffer at 64MB.
323
+ This size is dependent on your network speeds. We were focussed on WiFi
324
+ connections, so a larger buffer made sense. Too small and you will make the
325
+ download take longer than necessary to complete.&lt;/p&gt;
326
+ &lt;h2 id="where-to-go-from-here"&gt;
327
+ &lt;a href="#where-to-go-from-here"&gt;
328
+ Where to Go from Here
329
+ &lt;/a&gt;
330
+ &lt;/h2&gt;
331
+
332
+ &lt;p&gt;Now you have an understanding of one way that your application can eat up RAM.
333
+ If you only take away one thing from reading this post it&amp;rsquo;s this: for managing
334
+ memory allocations, streams are your friends. And you should be constantly
335
+ performance testing as you develop your application, unless you&amp;rsquo;re trying to
336
+ maximize one-star reviews in the App Store.&lt;/p&gt;
337
+ &lt;h2 id="gotchyas"&gt;
338
+ &lt;a href="#gotchyas"&gt;
339
+ Gotchyas
340
+ &lt;/a&gt;
341
+ &lt;/h2&gt;
342
+
343
+ &lt;p&gt;One final note on the code above: we did not end up going to production using
344
+ &lt;code&gt;UnityWebRequest&lt;/code&gt; on iOS. When we tried using a similar streaming solution
345
+ as above, we found that the request was not clearing from memory if it was
346
+ canceled due to the user sending the application to the background. Using the
347
+ Time Profiler Instrument showed that &lt;code&gt;NSURLSession&lt;/code&gt; objects were not being
348
+ cleaned up when the application paused and resumed, so eventually the CPU would
349
+ max out and crash. We had to seek an alternative solution for iOS using a native
350
+ plugin. However, in the final code we still used HTTP streaming directly into a
351
+ file via &lt;code&gt;FileStream&lt;/code&gt;. Just not wrapped up in &lt;code&gt;UnityWebRequest&lt;/code&gt; objects.&lt;/p&gt;
352
+ </content>
353
+ <summary>How to use Streams to more efficiently manage memory when downloading large files using Unity Engine.</summary>
354
+ <feedburner:origLink>https://robots.thoughtbot.com/avoiding-out-of-memory-crashes-on-mobile</feedburner:origLink></entry>
355
+ <entry>
356
+ <title>Solve Problems, not Strawmen</title>
357
+ <link rel="alternate" href="http://feedproxy.google.com/~r/GiantRobotsSmashingIntoOtherGiantRobots/~3/E6Xo6tl2AKk/you-aint-gonna-need-process" />
358
+ <author>
359
+ <name>Mike Burns</name>
360
+ </author>
361
+ <id>https://robots.thoughtbot.com/you-aint-gonna-need-process</id>
362
+ <published>2016-12-19T00:00:00+00:00</published>
363
+ <updated>2016-12-19T20:00:59Z</updated>
364
+ <content type="html">&lt;p&gt;It&amp;rsquo;s important to remember that solutions make little sense without problems,
365
+ and to &lt;strong&gt;avoid applying solutions in a vacuum&lt;/strong&gt;.&lt;/p&gt;
366
+
367
+ &lt;p&gt;Applying solutions within a vacuum &amp;ndash; for example, reading about VPNs and then
368
+ deciding to implement one for your team, without having a strong driving
369
+ impetus &amp;ndash; has a few downsides. Skipping the problem statement makes it
370
+ difficult to measure whether the solution works or how to iterate on it. It
371
+ also runs the risk of decreasing team happiness when solutions are not tethered
372
+ to reality.&lt;/p&gt;
373
+
374
+ &lt;p&gt;We avoid applying them unless needed in an effort to reduce bureaucracy and
375
+ process, so that developers can concentrate on developing, designers can
376
+ concentrate on designing, product owners can concentrate on prioritization, and
377
+ so on.&lt;/p&gt;
378
+
379
+ &lt;p&gt;To round this out we&amp;rsquo;ll present &lt;a href="https://thoughtbot.com/playbook"&gt;some solutions that we apply to
380
+ problems&lt;/a&gt;, along with some of the problems that they attack. Our
381
+ choice of the word &amp;ldquo;attack&amp;rdquo; here is meaningful: these solutions, despite their
382
+ name, do not &lt;em&gt;solve&lt;/em&gt; the problems; rather, they are part of a long-running
383
+ process of dealing with the problems. They might solve them entirely; they
384
+ might need refinement, or they might not work at all.&lt;/p&gt;
385
+ &lt;h2 id="acceptance-criteria"&gt;
386
+ &lt;a href="#acceptance-criteria"&gt;
387
+ Acceptance Criteria
388
+ &lt;/a&gt;
389
+ &lt;/h2&gt;
390
+
391
+ &lt;p&gt;Acceptance criteria is one way to attack the following problems:&lt;/p&gt;
392
+
393
+ &lt;ul&gt;
394
+ &lt;li&gt;Unrelated result: the ticket says one thing, the dev does another. If you
395
+ have another person accepting the stories, this will lead to lost time as the
396
+ dev and QA go back and forth on solutions. Without QA, this leads to an app
397
+ that you don&amp;rsquo;t recognize.&lt;/li&gt;
398
+ &lt;li&gt;The banana ticket: the developer knows how to implement the solution, but
399
+ doesn&amp;rsquo;t know &lt;a href="http://blog.teamtreehouse.com/when-is-a-user-story-done-acceptance-criteria-definition-done"&gt;when to stop&lt;/a&gt;, leading to an infinite refactoring of the entire
400
+ codebase under the guise of finishing this one ticket.&lt;/li&gt;
401
+ &lt;/ul&gt;
402
+
403
+ &lt;p&gt;To implement &lt;a href="http://nomad8.com/acceptance_criteria/"&gt;acceptance criteria&lt;/a&gt;, when writing tickets and stories for the
404
+ team, provide a detailed description of what the solution might look like &amp;ndash; a
405
+ description of when the story is finished and the ticket can be accepted by the
406
+ quality assurance team. &amp;ldquo;As an unconfirmed user, I cannot message anyone,&amp;rdquo; is a
407
+ quick example, but they can get bigger and more descriptive.&lt;/p&gt;
408
+ &lt;h2 id="retrospectives"&gt;
409
+ &lt;a href="#retrospectives"&gt;
410
+ Retrospectives
411
+ &lt;/a&gt;
412
+ &lt;/h2&gt;
413
+
414
+ &lt;p&gt;Retrospectives is one way to attack the following problems:&lt;/p&gt;
415
+
416
+ &lt;ul&gt;
417
+ &lt;li&gt;Unresolved conflict: the team is unable to communicate effectively during
418
+ conflict. Anger and resentment grow instead, buried and festering inside,
419
+ manifesting as passive-aggressive code review comments, poor collaboration,
420
+ low morale, people quitting, or an explosion of anger.&lt;/li&gt;
421
+ &lt;li&gt;Fights: instead of passive-aggressive, low-level frustration, the team could
422
+ express constant anger. Code review ends in tears.&lt;/li&gt;
423
+ &lt;/ul&gt;
424
+
425
+ &lt;p&gt;Scheduled, frequent, recurring retrospectives are a time for the team to
426
+ reflect on their happiness and internal relationship. These happen rain or
427
+ shine: it helps to practice communication in good times so that it becomes a
428
+ normal reflex in times of need. Some teams pair it with drinks at the end of a
429
+ workday; others do it mid-day as a normal part of the workday.&lt;/p&gt;
430
+ &lt;h2 id="standups"&gt;
431
+ &lt;a href="#standups"&gt;
432
+ Standups
433
+ &lt;/a&gt;
434
+ &lt;/h2&gt;
435
+
436
+ &lt;p&gt;Standup is one way to attack the following problems:&lt;/p&gt;
437
+
438
+ &lt;ul&gt;
439
+ &lt;li&gt;Isolation: people feel lonely working on remote teams or siloed projects.&lt;/li&gt;
440
+ &lt;li&gt;Othering: people on different teams have inhuman expectations of anothers&amp;rsquo;
441
+ team. This is expressed as increasing demands via faceless platforms like
442
+ &lt;a href="https://medium.com/@chrisjbatts/actually-slack-really-sucks-625802f1420a#.qf3n7xjdn"&gt;chat&lt;/a&gt; and ticket trackers, constant rejection of solutions, or warring
443
+ teams.&lt;/li&gt;
444
+ &lt;/ul&gt;
445
+
446
+ &lt;p&gt;A quick check-in once a day to catch everyone up on small details: what issue
447
+ or project people are working on, whether they are blocked, perhaps something
448
+ interesting that they learned, and general announcements. They are done
449
+ standing in an effort to &lt;a href="https://www.researchgate.net/publication/232529574_The_effects_of_stand-up_and_sit-down_meeting_formats_on_meeting_outcomes"&gt;keep them short&lt;/a&gt; (more fit teams may want to do them
450
+ standing on one leg instead).&lt;/p&gt;
451
+ &lt;h2 id="ticket-tracker"&gt;
452
+ &lt;a href="#ticket-tracker"&gt;
453
+ Ticket Tracker
454
+ &lt;/a&gt;
455
+ &lt;/h2&gt;
456
+
457
+ &lt;p&gt;A ticket tracker is one way to attack the following problems:&lt;/p&gt;
458
+
459
+ &lt;ul&gt;
460
+ &lt;li&gt;Duplicated work: multiple people open code reviews, only to find that another
461
+ review exists with that exact feature implemented. This kind of simple
462
+ miscommunication can be exacerbated by large teams, &lt;a href="http://basho.com/posts/technical/microservices-please-dont/"&gt;microservices&lt;/a&gt;,
463
+ distributed teams, and other communication challenges.&lt;/li&gt;
464
+ &lt;li&gt;Hurry up and wait: the marketing department waits until the feature is
465
+ shipped, and then hurries to advertise it (meanwhile they sat around
466
+ waiting).&lt;/li&gt;
467
+ &lt;li&gt;Surprise changes: the support team first learns that the UI has changed when
468
+ complaints roll in about the redesign; the CEO hears about the removal of her
469
+ favorite feature during a Q&amp;amp;A session at a conference.&lt;/li&gt;
470
+ &lt;/ul&gt;
471
+
472
+ &lt;p&gt;Ticket trackers are common, though often using different vocabulary. Trello
473
+ uses cards; Trajectory uses stories; Pivotal Tracker uses bugs, chores, and
474
+ stories. Jira does all of that plus provides visibility into metrics &amp;ndash; some
475
+ of which are projections, and others that are correct.&lt;/p&gt;
476
+ &lt;h2 id="story-or-jobs-to-be-done-format"&gt;
477
+ &lt;a href="#story-or-jobs-to-be-done-format"&gt;
478
+ Story or Jobs-To-Be-Done Format
479
+ &lt;/a&gt;
480
+ &lt;/h2&gt;
481
+
482
+ &lt;p&gt;Story format is one way to attack the following problems:&lt;/p&gt;
483
+
484
+ &lt;ul&gt;
485
+ &lt;li&gt;Mysterious business: the developer will happily implement a feature, lacking
486
+ the understanding of how it fits into the product or how it might be used.
487
+ Long term this leads to a disconnect between the code and the product &amp;ndash; the
488
+ domain-specific wording and language used throughout the app bears no
489
+ relation to the reality that it must model &amp;ndash; causing frustration among the
490
+ users and confusion when onboarding new developers.&lt;/li&gt;
491
+ &lt;li&gt;Unfollow-through: the developer implements the letter of the ticket, but not
492
+ the spirit, leading to situations where the feature is done but nothing
493
+ links to it; the JSON API functions but sends useless data; the user can
494
+ receive the password reset email but Gmail marks it as spam.&lt;/li&gt;
495
+ &lt;li&gt;Inflexibility: when the dev runs into complications, she pushes through
496
+ instead of re-evaluating for time sensitivity. This one solution is the only
497
+ one, and no compromises are entertained, regardless of how long this takes
498
+ and how it affects the user or the company.&lt;/li&gt;
499
+ &lt;/ul&gt;
500
+
501
+ &lt;p&gt;The story format phrases tasks in terms that the end user cares about, with an
502
+ explanation for &lt;a href="http://www.toyota-global.com/company/toyota_traditions/quality/mar_apr_2006.html"&gt;why&lt;/a&gt; the user might want the task done; similarly, the &lt;a href="https://robots.thoughtbot.com/converting-to-jobs-stories"&gt;jobs
503
+ story format&lt;/a&gt; puts the user&amp;rsquo;s context first and the &lt;a href="http://www.kitchensoap.com/2014/11/14/the-infinite-hows-or-the-dangers-of-the-five-whys/"&gt;motivation&lt;/a&gt; right in the
504
+ middle. This is all in contrast to the traditional task format, that focuses
505
+ only on what the developer must change in the code, with no explanation or
506
+ motivation.&lt;/p&gt;
507
+ &lt;h2 id="test-driven-design"&gt;
508
+ &lt;a href="#test-driven-design"&gt;
509
+ Test-Driven Design
510
+ &lt;/a&gt;
511
+ &lt;/h2&gt;
512
+
513
+ &lt;p&gt;TDD is one way to attack the following problems:&lt;/p&gt;
514
+
515
+ &lt;ul&gt;
516
+ &lt;li&gt;Blind fixes: the bug is fixed &amp;hellip; or is it? No one is quite sure, but
517
+ the new commit sure has a great description of how it could have fixed the
518
+ bug.&lt;/li&gt;
519
+ &lt;li&gt;Runtime whimsy: return values go unchecked, from &lt;a href="https://robots.thoughtbot.com/why-do-rubyists-test-so-completely"&gt;&lt;code&gt;nil&lt;/code&gt;&lt;/a&gt; to missing
520
+ files to failed credit card payments, leading to errors at runtime.&lt;/li&gt;
521
+ &lt;li&gt;Fear of deployment: the development workflow is running efficiently until it
522
+ comes time to actually merge the branch. Hesitation, followed by asking for
523
+ review after review, followed by begging others for reviews because no one
524
+ wants the responsibility of saying that it will work in production.&lt;/li&gt;
525
+ &lt;/ul&gt;
526
+
527
+ &lt;p&gt;Test-driven design (TDD) uses programmatic tests to drive the design and
528
+ architecture of the codebase. An incidental side effect is that the major
529
+ codepaths are tested, including error flows. Running the test suite exercises
530
+ all parts of the application, finding regressions in paths where bugs have been
531
+ found and fixed.&lt;/p&gt;
532
+ &lt;h2 id="refactoring"&gt;
533
+ &lt;a href="#refactoring"&gt;
534
+ Refactoring
535
+ &lt;/a&gt;
536
+ &lt;/h2&gt;
537
+
538
+ &lt;p&gt;Refactoring is one way to attack the following problems:&lt;/p&gt;
539
+
540
+ &lt;ul&gt;
541
+ &lt;li&gt;Unworkable feature: adding the new feature requires its own separate app or a
542
+ fragile connection through the database. What could be a simple button on a
543
+ Web page that performs a common activity takes days, weeks, or months to
544
+ implement.&lt;/li&gt;
545
+ &lt;li&gt;Hidden bug: you&amp;rsquo;ve traced the crash to one method, but that method was
546
+ written by a developer from two generations of coworkers ago, is 127
547
+ lines long, and the commit message was &amp;ldquo;it works&amp;rdquo;. The twenty code paths,
548
+ including liberal use of &lt;a href="https://docs.racket-lang.org/reference/cont.html#%28def._%28%28quote._%7E23%7E25kernel%29._call-with-escape-continuation%29%29"&gt;&lt;code&gt;return&lt;/code&gt;&lt;/a&gt;, obscure the source of the error.&lt;/li&gt;
549
+ &lt;/ul&gt;
550
+
551
+ &lt;p&gt;Refactoring is the process of shuffling code around without adding any features
552
+ or fixing any bugs. It is often the first step to implementing a new feature or
553
+ bug fix, carving a more clear path through the system, as part of the
554
+ &lt;a href="http://www.virtuouscode.com/2012/06/25/every-day-in-every-way/"&gt;red-green-refactor&lt;/a&gt; workflow.&lt;/p&gt;
555
+ &lt;h2 id="feature-flags"&gt;
556
+ &lt;a href="#feature-flags"&gt;
557
+ Feature Flags
558
+ &lt;/a&gt;
559
+ &lt;/h2&gt;
560
+
561
+ &lt;p&gt;Feature flags is one way to attack the following problems:&lt;/p&gt;
562
+
563
+ &lt;ul&gt;
564
+ &lt;li&gt;Market timing: the feature is implemented but the rest of the company still
565
+ isn&amp;rsquo;t ready for it. The support team needs to be trained on it, the
566
+ promotional announcement needs to be sent out, or the CEO needs to be
567
+ convinced that it&amp;rsquo;s a good idea.&lt;/li&gt;
568
+ &lt;li&gt;Questionable code: an isolated chunk of code &amp;ndash; a new file system, for
569
+ example &amp;ndash; is ready to be evaluated by willing and able participants, but
570
+ is not ready for public consumption until all the initial bugs have been
571
+ shaken out.&lt;/li&gt;
572
+ &lt;/ul&gt;
573
+
574
+ &lt;p&gt;Feature flags refers to hiding parts of the app behind a toggle, only shown to
575
+ some specific users or only enabled by an admin. These feature flags typically
576
+ differ from A/B testing in that they&amp;rsquo;re less about measurement and more about
577
+ hiding.&lt;/p&gt;
578
+ &lt;h2 id="code-review"&gt;
579
+ &lt;a href="#code-review"&gt;
580
+ Code Review
581
+ &lt;/a&gt;
582
+ &lt;/h2&gt;
583
+
584
+ &lt;p&gt;Code review is one way to attack the following problems:&lt;/p&gt;
585
+
586
+ &lt;ul&gt;
587
+ &lt;li&gt;Siloed development: developers work in isolation on specific categories of
588
+ projects &amp;ndash; one person on payment, another on API, a third on advertisement
589
+ targeting &amp;ndash; but on the same codebase. Features change around them and cruft
590
+ grows without any clear communication channel between devs.&lt;/li&gt;
591
+ &lt;li&gt;Poor code quality: the developers learn from blog posts and Web search
592
+ results instead of from each other, furthering their isolation. Coding styles
593
+ vary, and the same solution exists &lt;a href="http://lists.llvm.org/pipermail/llvm-dev/2016-October/106079.html"&gt;multiple times&lt;/a&gt; in the same repo.&lt;/li&gt;
594
+ &lt;/ul&gt;
595
+
596
+ &lt;p&gt;Reviewing code is the &lt;a href="http://www.geraldmweinberg.com/Site/Programming_Psychology.html"&gt;process&lt;/a&gt; of reading a diff: comparing a new commit with
597
+ what exists in the system. Often there is a focus on maintainability,
598
+ consistency, or knowledge transfer. Since it typically works on a diff, there
599
+ are fewer considerations for big picture harmony.&lt;/p&gt;
600
+ &lt;h2 id="git-workflow"&gt;
601
+ &lt;a href="#git-workflow"&gt;
602
+ Git Workflow
603
+ &lt;/a&gt;
604
+ &lt;/h2&gt;
605
+
606
+ &lt;p&gt;A consistent git workflow is one way to attack the following problems:&lt;/p&gt;
607
+
608
+ &lt;ul&gt;
609
+ &lt;li&gt;Unexplainable code: you find a strange line of code, the test explains
610
+ nothing, the commit message only says &amp;ldquo;initial commit&amp;rdquo;, the ticket tracker
611
+ was replaced twice since the project started, as was the project manager. Why
612
+ is this solution the right one, and why does the mysterious test enforce it?&lt;/li&gt;
613
+ &lt;li&gt;Bus factor one: the developer works alone, deploys a JAR, and leaves no
614
+ comments. Then she quits and the new dev is onboarded. I hope you enjoyed
615
+ this short horror story.&lt;/li&gt;
616
+ &lt;/ul&gt;
617
+
618
+ &lt;p&gt;Git provides enough plumbing to hang yourself. Maybe that&amp;rsquo;s not the expression.
619
+ Regardless, there&amp;rsquo;s no one way to use git, and multiple right ways. From branch
620
+ names to merge strategies to &lt;a href="https://robots.thoughtbot.com/5-useful-tips-for-a-better-commit-message"&gt;commit message&lt;/a&gt; content, it&amp;rsquo;s possible to have
621
+ &lt;a href="https://github.com/thoughtbot/guides/tree/master/protocol/git"&gt;a unified version control process&lt;/a&gt;.&lt;/p&gt;
622
+ &lt;h2 id="deployment-process"&gt;
623
+ &lt;a href="#deployment-process"&gt;
624
+ Deployment Process
625
+ &lt;/a&gt;
626
+ &lt;/h2&gt;
627
+
628
+ &lt;p&gt;A deploy process is one way to attack the following problems:&lt;/p&gt;
629
+
630
+ &lt;ul&gt;
631
+ &lt;li&gt;Expensive downtime: each second of downtime costs more in lost sales than
632
+ each second of development time costs in salary.&lt;/li&gt;
633
+ &lt;li&gt;External failures: the deployment depends on a set of external services
634
+ providing DNS, caching and content delivery, uptime monitoring, error
635
+ reporting, and so on, each of which has their own failure modes.&lt;/li&gt;
636
+ &lt;li&gt;Regulations: strict compliance with the law requires that very few people
637
+ have access to the database or the production servers.&lt;/li&gt;
638
+ &lt;/ul&gt;
639
+
640
+ &lt;p&gt;Going from development to production takes a few steps, which means it can be
641
+ scripted. The runnable script can live near a human-readable script, separately
642
+ describing who can deploy, what steps to take when it fails, what to do about
643
+ downtime, and how to announce it. Combined, the program and documentation
644
+ around it make up the deployment process.&lt;/p&gt;
645
+ &lt;h2 id="horizontal-scaling"&gt;
646
+ &lt;a href="#horizontal-scaling"&gt;
647
+ Horizontal Scaling
648
+ &lt;/a&gt;
649
+ &lt;/h2&gt;
650
+
651
+ &lt;p&gt;Scaling is one way to attack the following problems:&lt;/p&gt;
652
+
653
+ &lt;ul&gt;
654
+ &lt;li&gt;Concurrent users: the service is immediately more popular and needs to handle
655
+ twice as many users as before. They don&amp;rsquo;t necessarily need to use the full
656
+ service, but at least need to have enough working to get their job done.&lt;/li&gt;
657
+ &lt;li&gt;Concurrent processing: the algorithm can be subdivided into smaller,
658
+ independent problems, but each problem would take its own computer to solve.&lt;/li&gt;
659
+ &lt;/ul&gt;
660
+
661
+ &lt;p&gt;Scaling horizontally presents as spinning up more servers to handle the load;
662
+ this is in comparison with vertical scaling, where the CPU is sped up or the
663
+ RAM increased. The new servers come up quickly (&amp;ldquo;instantly&amp;rdquo;), run the same
664
+ software, and can be reduced when needed.&lt;/p&gt;
665
+ &lt;h2 id="and-more"&gt;
666
+ &lt;a href="#and-more"&gt;
667
+ And more
668
+ &lt;/a&gt;
669
+ &lt;/h2&gt;
670
+
671
+ &lt;p&gt;This is just a tiny selection of the solutions that we have seen to real
672
+ problems over the years. Some are for fancy ideas &amp;ndash; feature flags and story
673
+ format are not common to every team &amp;ndash; and some are sacred cows, like TDD and
674
+ refactoring.&lt;/p&gt;
675
+
676
+ &lt;p&gt;Your homework, dear reader, is to think of what problems you are solving and
677
+ what solutions are not carrying their weight. Let us know what you learn while
678
+ reflecting; we are always looking for &lt;a href="https://thoughtbot.com/playbook"&gt;processes we can drop&lt;/a&gt;!&lt;/p&gt;
679
+ </content>
680
+ <summary>Avoid applying solutions in a vacuum.</summary>
681
+ <feedburner:origLink>https://robots.thoughtbot.com/you-aint-gonna-need-process</feedburner:origLink></entry>
682
+ </feed>