<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:ns0="http://www.w3.org/2005/Atom" version="2.0"><channel><title>Random Thoughts (Posts about best-practices)</title><link>https://carreau.github.io/</link><description /><ns0:link href="https://carreau.github.io/categories/best-practices.xml" rel="self" type="application/rss+xml" /><language>en</language><lastBuildDate>Wed, 27 Oct 2021 20:18:00 GMT</lastBuildDate><generator>Nikola (getnikola.com)</generator><docs>http://blogs.law.harvard.edu/tech/rss</docs><item><title>The Pleasure of deleting code</title><link>https://carreau.github.io/posts/34-the-pleasure-of-deleting-code.md/</link><dc:creator>Matthias Bussonnier</dc:creator><description>&lt;div&gt;&lt;h2&gt;Good Code is Deleted Code&lt;/h2&gt;
&lt;p&gt;The only code without bugs is &lt;a href="https://github.com/kelseyhightower/nocode"&gt;no
code&lt;/a&gt;. And the less code you have,
the less mental load as well. This is why it is often a pleasure to delete a lot
of code. &lt;/p&gt;
&lt;p&gt;In IPython we recently bumped the version number to 7.0 and &lt;a href="https://github.com/ipython/ipython/pull/10833"&gt;dropped support for
Python 3.3&lt;/a&gt;. This was the
occasion to clean, and remove a lots of code that insure compatibility with
multiple minor Python version, and while it may seem easy it required a lot of
thinking ahead of time to make the process simple. &lt;/p&gt;
&lt;h3&gt;Finding what can (and should be deleted)&lt;/h3&gt;
&lt;p&gt;The hardest part is not deleting the code itself, but finding what can be
deleted. In many compiled languages, the compiler may help you, but with Python
it can be quite tougher, and some of Python usual practices make it harder.&lt;/p&gt;
&lt;p&gt;Here are a few tips on how to prepare your code (when you write it) for
deletion. &lt;/p&gt;
&lt;h4&gt;EAFP vs LBYL&lt;/h4&gt;
&lt;p&gt;Python tend to be more on the Easier to ask Forgiveness than Permission, than
Look Before You Leap. It is thus common to see &lt;a href="https://github.com/ipython/ipython/issues/11068"&gt;code like&lt;/a&gt;:&lt;/p&gt;
&lt;pre class="code literal-block"&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; 
     &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;importlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;reload&lt;/span&gt; 
&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="ne"&gt;ImportError&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; 
     &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;imp&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;reload&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;In this particular case though, why do we use the try/except ? Unless there is
a comment attached, it is hard guess that &lt;code&gt;from imp import reload&lt;/code&gt; was
deprecated since python 3.4, the comment can easily get out of sync with the
actual code. &lt;/p&gt;
&lt;p&gt;A better way would be to explicitly check &lt;code&gt;sys.version_info&lt;/code&gt;&lt;/p&gt;
&lt;pre class="code literal-block"&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;version_info&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
     &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;imp&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;reload&lt;/span&gt; 
&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
     &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;importlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;reload&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;(Note, tuple from unequal length can be compared in python). &lt;/p&gt;
&lt;p&gt;It is now obvious which code should be removed and when. You can see that as
"Explicit is better than implicit" rule. &lt;/p&gt;
&lt;h3&gt;Deprecated code&lt;/h3&gt;
&lt;p&gt;Removing legacy deprecated code is also always a challenge, as you may be
worried of other library might be still relying deprecation. To help with that
let's see how we can improve typical deprecation, here is a typical deprecated
method from IPython::&lt;/p&gt;
&lt;pre class="code literal-block"&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;def unicode_std_stream(stream='stdout'):
    """DEPRECATED"""
    warn("IPython.utils.io.unicode_std_stream is deprecated", DeprecationWarning)
    ...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;How much are you confident you can remove this ? A few question should pop into
your head:
  - Since when has this function been deprecated ? &lt;/p&gt;
&lt;pre class="code literal-block"&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;def unicode_std_stream(stream='stdout'):
    """DEPRECATED"""
    warn("IPython.utils.io.unicode_std_stream is deprecated since IPython 4.0", DeprecationWarning)
    ...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;With this new snippet I'm confident it's been 3 versions and I am more willing
to delete. This also helps downstream libraries to know whether they need
conditional code or now. I'm still unsure downstream maintainer have updated
their code. Let's add a stacklevel (to help them find where the deprecated
function is used, and add more informations about how they can replace code uses
this function:&lt;/p&gt;
&lt;pre class="code literal-block"&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;def unicode_std_stream(stream='stdout'):
    """DEPRECATED, moved to nbconvert.utils.io"""
    warn("IPython.utils.io.unicode_std_stream has moved to nbconvert.utils.io since IPython 4.0", DeprecationWarning, stacklevel=2)
    ...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Well with this information I'm even more confident downstream maintainer have
updated their code. They have an actionable item: replace one import for
another, and are more likely to do that, than dig for 1h in history to figure
out what to do. &lt;/p&gt;
&lt;h2&gt;TLDR&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Be explicit in your conditional import that depends on version of underlying
python or library. &lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;take time to write good deprecation warning with : &lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;Stacklevel (=2 most of the time) &lt;/li&gt;
&lt;li&gt;Since When it was deprecated.&lt;/li&gt;
&lt;li&gt;What should replace deprecated call for consumers. &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The time you put in these will greatly help your downstream consumers, and
benefit you later to simplify getting rid of lots of code easily.&lt;/p&gt;&lt;/div&gt;
&lt;/body&gt;&lt;/html&gt;

</description><category>best-practices</category><category>open-source</category><category>python</category><guid>https://carreau.github.io/posts/34-the-pleasure-of-deleting-code.md/</guid><pubDate>Tue, 03 Apr 2018 13:30:00 GMT</pubDate></item></channel></rss>