<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[Ivan Skodje]]></title><description><![CDATA[Ivan Skodje]]></description><link>https://ivanskodje.com/</link><image><url>https://ivanskodje.com/favicon.png</url><title>Ivan Skodje</title><link>https://ivanskodje.com/</link></image><generator>Ghost 3.25</generator><lastBuildDate>Sat, 27 Dec 2025 13:36:36 GMT</lastBuildDate><atom:link href="https://ivanskodje.com/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[Setup Pi-hole on Raspberry Pi (2023)]]></title><description><![CDATA[Discover how to set up Pi-Hole on a Raspberry Pi for network-wide ad-blocking, malware protection, and online privacy. Our step-by-step guide makes installation easy, but note that Pi-Hole doesn't block YouTube video ads.]]></description><link>https://ivanskodje.com/setup-pi-hole-on-raspberry-pi/</link><guid isPermaLink="false">6432977a46a7d20001bc0b38</guid><category><![CDATA[DevOps]]></category><category><![CDATA[GNU/Linux]]></category><dc:creator><![CDATA[Ivan Skodje]]></dc:creator><pubDate>Sun, 09 Apr 2023 11:37:31 GMT</pubDate><media:content url="https://ivanskodje.com/content/images/2023/04/Pasted-image-20230409132620.png" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://ivanskodje.com/content/images/2023/04/Pasted-image-20230409132620.png" alt="Setup Pi-hole on Raspberry Pi (2023)"><p><a href="https://pi-hole.net/">Pi-Hole</a> is a DNS-based filtering tool that can block ads, tracking, and malicious sites on your entire network. It takes around 10-30 minutes to set up.</p>
<blockquote>
<p>Important:</p>
<ul>
<li>Pi-hole is unfortunately not great at blocking YouTube video ads. For that you would have to use AdBlock or µOrigin extension on your browser.</li>
</ul>
</blockquote>
<h2 id="prerequisites">Prerequisites</h2>
<ul>
<li>Raspberry Pi compatible with Raspberry Pi OS</li>
<li>Micro SD card</li>
<li>Micro USB power supply</li>
<li>MicroSD card to USB adapter for installing the OS</li>
<li>Micro B to Ethernet dongle and Ethernet cable if your Pi doesn't have built-in WiFi</li>
</ul>
<h2 id="instructions">Instructions</h2>
<h3 id="step1installraspberrypios">Step 1: Install Raspberry Pi OS</h3>
<ol>
<li>Download and install the latest version of Raspberry Pi OS from the <a href="https://www.raspberrypi.com/software/">official website</a>.</li>
<li>Download, install, and run the <a href="https://www.raspberrypi.com/software/">Raspberry Pi Imager</a></li>
<li>Choose the OS and microSD card storage, and configure the settings to enable SSH and a user.</li>
<li>Install the OS onto the microSD card and insert it into the Raspberry Pi.</li>
<li>Plug in the Ethernet dongle and Ethernet cable into the Raspberry Pi if it doesn't have built-in WiFi.</li>
<li>Power up the Raspberry Pi.</li>
</ol>
<h3 id="step2connecttotheraspberrypiviassh">Step 2: Connect to the Raspberry Pi via SSH</h3>
<ol>
<li>Find the IP address assigned to the Raspberry Pi from your router.</li>
<li>Open a terminal and type <code>ssh pi@&lt;ip_address&gt;</code> (replace <code>&lt;ip_address&gt;</code> with the IP address of your Raspberry Pi).</li>
<li>Enter your username and password.</li>
</ol>
<h3 id="step3installpihole">Step 3: Install Pi-hole</h3>
<ol>
<li>Update your Raspberry Pi by running <code>sudo apt-get update &amp;&amp; sudo apt-get upgrade</code>.</li>
<li>Install Pi-Hole by running <code>curl -sSL https://install.pi-hole.net | bash</code> <a href="https://github.com/pi-hole/pi-hole/#one-step-automated-install">Source</a>.</li>
<li>Follow the on-screen instructions to complete the installation.</li>
<li>Save the URL to the admin panel presented at the end of the installation process.</li>
</ol>
<h3 id="step4configureyourrouter">Step 4: Configure your router</h3>
<ol>
<li>Log in to your router's web interface.</li>
<li>Find the DNS settings and change the primary DNS server to the IP address of your Raspberry Pi.</li>
</ol>
<h3 id="step5testpihole">Step 5: Test Pi Hole</h3>
<ol>
<li>Visit <a href="https://cnn.com/">https://cnn.com</a>.</li>
<li>If you don't see ads, the setup is successful.</li>
<li>If you still see ads, turn your WiFi off and on.</li>
</ol>
<p>Congratulations! You've successfully set up Pi Hole on your Raspberry Pi.</p>
<hr>
<h2 id="knownissues">Known Issues</h2>
<h3 id="cantsetlocalemakesurelc_andlangarecorrect">&quot;Can't set locale; make sure $LC_* and $LANG are correct!&quot;</h3>
<pre><code class="language-bash">apt-listchanges: Can't set locale; make sure $LC_* and $LANG are correct!
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
	LANGUAGE = (unset),
	LC_ALL = (unset),
	LC_TIME = &quot;nb_NO.UTF-8&quot;,
	LC_MONETARY = &quot;nb_NO.UTF-8&quot;,
	LC_ADDRESS = &quot;nb_NO.UTF-8&quot;,
	LC_TELEPHONE = &quot;nb_NO.UTF-8&quot;,
	LC_NAME = &quot;nb_NO.UTF-8&quot;,
	LC_MEASUREMENT = &quot;nb_NO.UTF-8&quot;,
	LC_IDENTIFICATION = &quot;nb_NO.UTF-8&quot;,
	LC_NUMERIC = &quot;nb_NO.UTF-8&quot;,
	LC_PAPER = &quot;nb_NO.UTF-8&quot;,
	LANG = &quot;en_GB.UTF-8&quot;
    are supported and installed on your system.
perl: warning: Falling back to a fallback locale (&quot;en_GB.UTF-8&quot;).
</code></pre>
<p>This warning won't break anything, but you can fix it by following these steps:</p>
<ol>
<li>Run the command <code>sudo dpkg-reconfigure locales</code> to generate locales.</li>
<li>Press &quot;continue&quot; unless you need to add more locales. <a href="https://unix.stackexchange.com/questions/269159/problem-of-cant-set-locale-make-sure-lc-and-lang-are-correct">Source</a></li>
</ol>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[How to Setup DigitalOcean Droplet Server]]></title><description><![CDATA[<blockquote>We are going to be setting up a fresh and secure DigitalOcean droplet server, using Ubuntu 20.04. </blockquote><hr><h2 id="prerequisites">Prerequisites</h2><ul><li>Access to DigitalOcean for setting up a new project &amp; droplet (~$5/mo)</li><li>Your own domain name that you can use to link with DigitalOcean</li></ul><h3 id="what-we-will-be-doing">What we will be doing</h3><ul><li>Create</li></ul>]]></description><link>https://ivanskodje.com/setup-digitalocean-droplet-server/</link><guid isPermaLink="false">60b1450ede267d000118fce1</guid><category><![CDATA[DevOps]]></category><dc:creator><![CDATA[Ivan Skodje]]></dc:creator><pubDate>Tue, 01 Jun 2021 16:01:16 GMT</pubDate><media:content url="https://ivanskodje.com/content/images/2021/06/thumbnail_droplet_1.png" medium="image"/><content:encoded><![CDATA[<blockquote>We are going to be setting up a fresh and secure DigitalOcean droplet server, using Ubuntu 20.04. </blockquote><hr><h2 id="prerequisites">Prerequisites</h2><ul><li>Access to DigitalOcean for setting up a new project &amp; droplet (~$5/mo)</li><li>Your own domain name that you can use to link with DigitalOcean</li></ul><h3 id="what-we-will-be-doing">What we will be doing</h3><ul><li>Create a new Project</li><li>Create new Droplet</li><li>Link your domain name with DigitalOcean</li><li>	&gt; Update your own domain's nameservers</li><li>Secure the Server</li><li>	&gt; Login to the server via SSH</li><li>	&gt; Update and Upgrade</li><li>	&gt; Create a new user</li><li>	&gt; Disable root user</li><li>	&gt; Change Default UFW Firewall Port</li></ul><hr><h2 id="create-a-new-project">Create a new Project</h2><img src="https://ivanskodje.com/content/images/2021/06/thumbnail_droplet_1.png" alt="How to Setup DigitalOcean Droplet Server"><p>Login to <a href="https://www.digitalocean.com/?refcode=414fcaae8e12">digitalocean.com</a> <em>(thank you for using my referral link)</em>, and create a New Project from the top right.</p><figure class="kg-card kg-image-card"><img src="https://ivanskodje.com/content/images/2021/05/image-30.png" class="kg-image" alt="How to Setup DigitalOcean Droplet Server"></figure><p>Fill in the project information of your choice and hit Create Project.</p><figure class="kg-card kg-image-card"><img src="https://ivanskodje.com/content/images/2021/05/000-create-project.gif" class="kg-image" alt="How to Setup DigitalOcean Droplet Server"></figure><p></p><p>Skip the second step unless you already use DigitalOcean and have resources you would like to move.</p><figure class="kg-card kg-image-card"><img src="https://ivanskodje.com/content/images/2021/05/image-31.png" class="kg-image" alt="How to Setup DigitalOcean Droplet Server" srcset="https://ivanskodje.com/content/images/size/w600/2021/05/image-31.png 600w, https://ivanskodje.com/content/images/2021/05/image-31.png 829w" sizes="(min-width: 720px) 720px"></figure><p></p><h2 id="create-a-new-droplet">Create a new Droplet</h2><p>Create: <em>Droplet.</em></p><figure class="kg-card kg-image-card"><img src="https://ivanskodje.com/content/images/2021/05/001-create-droplet.gif" class="kg-image" alt="How to Setup DigitalOcean Droplet Server"></figure><p></p><p><em>Cho</em>ose an image: <em>Ubuntu 21.04 x64</em></p><figure class="kg-card kg-image-card"><img src="https://ivanskodje.com/content/images/2021/05/image-22.png" class="kg-image" alt="How to Setup DigitalOcean Droplet Server" srcset="https://ivanskodje.com/content/images/size/w600/2021/05/image-22.png 600w, https://ivanskodje.com/content/images/size/w1000/2021/05/image-22.png 1000w, https://ivanskodje.com/content/images/2021/05/image-22.png 1155w" sizes="(min-width: 720px) 720px"></figure><p></p><p>Choose a plan: <em>Basic</em></p><figure class="kg-card kg-image-card"><img src="https://ivanskodje.com/content/images/2021/05/image-23.png" class="kg-image" alt="How to Setup DigitalOcean Droplet Server" srcset="https://ivanskodje.com/content/images/size/w600/2021/05/image-23.png 600w, https://ivanskodje.com/content/images/size/w1000/2021/05/image-23.png 1000w, https://ivanskodje.com/content/images/2021/05/image-23.png 1372w" sizes="(min-width: 720px) 720px"></figure><p></p><p>CPU options: <em>Regular Intel with SSD, $5/mo</em></p><figure class="kg-card kg-image-card"><img src="https://ivanskodje.com/content/images/2021/05/002-choose.gif" class="kg-image" alt="How to Setup DigitalOcean Droplet Server"></figure><p></p><p>Choose a datacenter region: <em>(your choice, closer is better)</em></p><figure class="kg-card kg-image-card"><img src="https://ivanskodje.com/content/images/2021/05/image-24.png" class="kg-image" alt="How to Setup DigitalOcean Droplet Server" srcset="https://ivanskodje.com/content/images/size/w600/2021/05/image-24.png 600w, https://ivanskodje.com/content/images/size/w1000/2021/05/image-24.png 1000w, https://ivanskodje.com/content/images/2021/05/image-24.png 1377w" sizes="(min-width: 720px) 720px"></figure><p></p><p>Authentication: <em>Choose a temp password (we will change it later)</em> </p><figure class="kg-card kg-image-card"><img src="https://ivanskodje.com/content/images/2021/05/003-password.gif" class="kg-image" alt="How to Setup DigitalOcean Droplet Server"></figure><p></p><p>Finalize and create: <em>1 Droplet, choose a hostname (or use default)</em></p><figure class="kg-card kg-image-card"><img src="https://ivanskodje.com/content/images/2021/05/image-26.png" class="kg-image" alt="How to Setup DigitalOcean Droplet Server" srcset="https://ivanskodje.com/content/images/size/w600/2021/05/image-26.png 600w, https://ivanskodje.com/content/images/size/w1000/2021/05/image-26.png 1000w, https://ivanskodje.com/content/images/2021/05/image-26.png 1371w" sizes="(min-width: 720px) 720px"></figure><p></p><p>Choose whether or not you wish to enable backups.</p><figure class="kg-card kg-image-card"><img src="https://ivanskodje.com/content/images/2021/05/image-25.png" class="kg-image" alt="How to Setup DigitalOcean Droplet Server" srcset="https://ivanskodje.com/content/images/size/w600/2021/05/image-25.png 600w, https://ivanskodje.com/content/images/size/w1000/2021/05/image-25.png 1000w, https://ivanskodje.com/content/images/2021/05/image-25.png 1371w" sizes="(min-width: 720px) 720px"></figure><p></p><p>Create Droplet:</p><figure class="kg-card kg-image-card"><img src="https://ivanskodje.com/content/images/2021/05/image-27.png" class="kg-image" alt="How to Setup DigitalOcean Droplet Server" srcset="https://ivanskodje.com/content/images/size/w600/2021/05/image-27.png 600w, https://ivanskodje.com/content/images/size/w1000/2021/05/image-27.png 1000w, https://ivanskodje.com/content/images/2021/05/image-27.png 1363w" sizes="(min-width: 720px) 720px"></figure><figure class="kg-card kg-image-card"><img src="https://ivanskodje.com/content/images/2021/05/image-28.png" class="kg-image" alt="How to Setup DigitalOcean Droplet Server" srcset="https://ivanskodje.com/content/images/size/w600/2021/05/image-28.png 600w, https://ivanskodje.com/content/images/size/w1000/2021/05/image-28.png 1000w, https://ivanskodje.com/content/images/2021/05/image-28.png 1363w" sizes="(min-width: 720px) 720px"></figure><p></p><h2 id="link-your-domain-name-with-digitalocean">Link your domain name with DigitalOcean</h2><hr><h3 id="update-your-own-domain-s-nameservers">Update your own domain's nameservers</h3><p>From wherever you bought your domain name, you will to setup the nameservers to point it towards DigitalOcean's. For example, on namecheap.com you go to your domain settings, and set the nameserver addresses:</p><figure class="kg-card kg-image-card"><img src="https://ivanskodje.com/content/images/2021/05/image-33.png" class="kg-image" alt="How to Setup DigitalOcean Droplet Server" srcset="https://ivanskodje.com/content/images/size/w600/2021/05/image-33.png 600w, https://ivanskodje.com/content/images/2021/05/image-33.png 878w" sizes="(min-width: 720px) 720px"></figure><blockquote>ns1.digitalocean.com<br>ns2.digitalocean.com<br>ns3.digitalocean.com</blockquote><p>Consult your domain name provider if you need help setting up custom nameservers.</p><hr><p>Manage DNS on DigitalOcean: <em>Select it on the bottom of your project page.</em></p><figure class="kg-card kg-image-card"><img src="https://ivanskodje.com/content/images/2021/05/image-29.png" class="kg-image" alt="How to Setup DigitalOcean Droplet Server"></figure><blockquote>If for some reason you cannot find it, find <strong>Networking</strong> on the left menu and choose Domains</blockquote><p>Add a domain: Y<em>our own domain name</em></p><figure class="kg-card kg-image-card"><img src="https://ivanskodje.com/content/images/2021/05/004-add-domain.gif" class="kg-image" alt="How to Setup DigitalOcean Droplet Server"></figure><p></p><p>Select your own domain name.</p><figure class="kg-card kg-image-card"><img src="https://ivanskodje.com/content/images/2021/05/image-32.png" class="kg-image" alt="How to Setup DigitalOcean Droplet Server" srcset="https://ivanskodje.com/content/images/size/w600/2021/05/image-32.png 600w, https://ivanskodje.com/content/images/size/w1000/2021/05/image-32.png 1000w, https://ivanskodje.com/content/images/2021/05/image-32.png 1361w" sizes="(min-width: 720px) 720px"></figure><p></p><p>Create a new record: <em>This is going to point to the default HTTP page we will get when we setup Nginx.</em></p><figure class="kg-card kg-image-card"><img src="https://ivanskodje.com/content/images/2021/05/005-create-new-record.gif" class="kg-image" alt="How to Setup DigitalOcean Droplet Server"></figure><blockquote>@ indicates we want to use the plain hostname for our default web server.<br><br>You can also enter a hostname if you intend to use a subdomain name. <br>If you enter * you will redirect all sub-domains to the default web-server.</blockquote><p></p><figure class="kg-card kg-image-card"><img src="https://ivanskodje.com/content/images/2021/05/image-34.png" class="kg-image" alt="How to Setup DigitalOcean Droplet Server" srcset="https://ivanskodje.com/content/images/size/w600/2021/05/image-34.png 600w, https://ivanskodje.com/content/images/size/w1000/2021/05/image-34.png 1000w, https://ivanskodje.com/content/images/2021/05/image-34.png 1385w" sizes="(min-width: 720px) 720px"></figure><p></p><p>If you open PowerShell or any commandline, and enter <code>ping &lt;yourdomain/ip&gt;</code>, you will hopefully get the IP address to the server. This means the domain name is ready to be used.</p><figure class="kg-card kg-image-card"><img src="https://ivanskodje.com/content/images/2021/05/image-37.png" class="kg-image" alt="How to Setup DigitalOcean Droplet Server" srcset="https://ivanskodje.com/content/images/size/w600/2021/05/image-37.png 600w, https://ivanskodje.com/content/images/2021/05/image-37.png 695w"></figure><blockquote>Note that it may take a little while before the new record works. <br>If your domain is completely new, it may take 12-48h.</blockquote><p></p><h2 id="secure-the-server">Secure the Server</h2><h3 id="login-to-the-server-via-ssh">Login to the server via SSH</h3><p>Open PowerShell, or any other command-line tool that has SSH capabilities. </p><blockquote>SSH is used to connect to the server terminal so we can start working with it</blockquote><p></p><p>SSH in to your server:</p><pre><code class="language-Shell">ssh root@yourdomain</code></pre><p>Type "yes" to confirm that you wish to continue, and enter your password. </p><blockquote>Your password is not visible while typing</blockquote><figure class="kg-card kg-image-card"><img src="https://ivanskodje.com/content/images/2021/05/006-ssh-to-server.gif" class="kg-image" alt="How to Setup DigitalOcean Droplet Server"></figure><p>Once you have succeeded logging in, you will be greeted by a warm message:</p><figure class="kg-card kg-image-card"><img src="https://ivanskodje.com/content/images/2021/05/image-38.png" class="kg-image" alt="How to Setup DigitalOcean Droplet Server" srcset="https://ivanskodje.com/content/images/size/w600/2021/05/image-38.png 600w, https://ivanskodje.com/content/images/2021/05/image-38.png 938w" sizes="(min-width: 720px) 720px"></figure><h3 id="update-and-upgrade">Update and Upgrade</h3><pre><code class="language-Shell">sudo apt update -y</code></pre><figure class="kg-card kg-image-card"><img src="https://ivanskodje.com/content/images/2021/05/007-update.gif" class="kg-image" alt="How to Setup DigitalOcean Droplet Server"></figure><blockquote>-y is to accept the update in advance (otherwise you have to manually enter for the updates)</blockquote><pre><code class="language-Shell">sudo apt upgrade -y</code></pre><figure class="kg-card kg-image-card"><img src="https://ivanskodje.com/content/images/2021/05/008-upgrade.gif" class="kg-image" alt="How to Setup DigitalOcean Droplet Server"></figure><p>This will ensure most of our software is up to date.</p><h4 id="upgrade-event-daemons-using-outdated-libraries">Upgrade Event: Daemons using outdated libraries</h4><p>If you are getting a popup during the upgrade process asking you which services should be restarted; unless you know what you are doing, you should keep the default and select OK by pressing <strong>Enter</strong>.</p><figure class="kg-card kg-image-card"><img src="https://ivanskodje.com/content/images/2021/05/image-39.png" class="kg-image" alt="How to Setup DigitalOcean Droplet Server" srcset="https://ivanskodje.com/content/images/size/w600/2021/05/image-39.png 600w, https://ivanskodje.com/content/images/2021/05/image-39.png 718w"></figure><p></p><h3 id="create-a-new-user">Create a new user</h3><p>We do not ever want a root account available to the outside world. We can remedy that security risk by creating a new user with sudo permissions, and disabling root. </p><blockquote>"sudo permissions" is a fancy-pancy way of saying administrator privileges.</blockquote><p></p><p>Add new user:</p><pre><code class="language-Shell">adduser &lt;your-username&gt;</code></pre><figure class="kg-card kg-image-card"><img src="https://ivanskodje.com/content/images/2021/05/009-adduser.gif" class="kg-image" alt="How to Setup DigitalOcean Droplet Server"></figure><p></p><p>Add the new user to the <strong>sudo</strong> group:</p><pre><code class="language-Shell">usermod -aG sudo &lt;your-username&gt;</code></pre><figure class="kg-card kg-image-card"><img src="https://ivanskodje.com/content/images/2021/05/010-add-group.gif" class="kg-image" alt="How to Setup DigitalOcean Droplet Server"></figure><p></p><p>Before we even think about disabling root, we want to ensure that our new user has the appropriate permissions to access root commands.</p><p>Login as the user:</p><pre><code class="language-Shell">su - &lt;your-username&gt;</code></pre><figure class="kg-card kg-image-card"><img src="https://ivanskodje.com/content/images/2021/05/011-login-as-user.gif" class="kg-image" alt="How to Setup DigitalOcean Droplet Server"></figure><p></p><p>Verify that you are in the sudo group:</p><pre><code class="language-Shell">groups</code></pre><p></p><p>Verify that you can access a root folder:</p><pre><code class="language-Shell">sudo ls -al /root</code></pre><figure class="kg-card kg-image-card"><img src="https://ivanskodje.com/content/images/2021/05/012-verify-sudo-permissions.gif" class="kg-image" alt="How to Setup DigitalOcean Droplet Server"></figure><p>If you did not receive any permission errors, we are good to go. </p><blockquote>If you got any errors, share them with us!</blockquote><p></p><p>Type exit to leave as the user and return as the root user.</p><pre><code class="language-Shell">exit</code></pre><figure class="kg-card kg-image-card"><img src="https://ivanskodje.com/content/images/2021/05/013-exit-user.gif" class="kg-image" alt="How to Setup DigitalOcean Droplet Server"></figure><p></p><h3 id="disable-root-user">Disable root user</h3><p>Now that we have ensured that we have a new user we can use to replace the "root" user, we want to disable the root user so no evil can be done with it.</p><p></p><p>Disable root user:</p><pre><code class="language-Shell">passwd -l root</code></pre><figure class="kg-card kg-image-card"><img src="https://ivanskodje.com/content/images/2021/05/014-disable-root-and-quit.gif" class="kg-image" alt="How to Setup DigitalOcean Droplet Server"></figure><p>Disconnect as the root user:</p><pre><code>exit</code></pre><p></p><p>SSH into your server again with your new username and password:</p><pre><code class="language-Shell">ssh &lt;username&gt;@&lt;server&gt;</code></pre><figure class="kg-card kg-image-card"><img src="https://ivanskodje.com/content/images/2021/05/015-relogin-new-user.gif" class="kg-image" alt="How to Setup DigitalOcean Droplet Server"></figure><h3 id="change-default-ufw-firewall-port">Change Default UFW Firewall Port</h3><blockquote><strong>NB: Make sure you complete this part, as we will be disabling the default SSH port when we activate UFW. </strong><br><strong>If you quit before re-enabling SSH you will no longer be able to SSH into the server.</strong></blockquote><p>We are going to be using the UFW firewall. Check the status, and enable it if it isn't already so.</p><pre><code class="language-Shell">sudo ufw status
sudo ufw enable</code></pre><figure class="kg-card kg-image-card"><img src="https://ivanskodje.com/content/images/2021/05/016-enable-ufw.gif" class="kg-image" alt="How to Setup DigitalOcean Droplet Server"></figure><p></p><p>We currently do not have any open ports. We want to make sure we have SSH enabled, but we will do so for a different port. We are going to set the port to  <strong>13022</strong>, but you can choose any between 1024 and 65535. Remember this, as we will have to change the SSH configuration as well later.</p><pre><code class="language-Shell">sudo ufw allow 13022/tcp</code></pre><figure class="kg-card kg-image-card"><img src="https://ivanskodje.com/content/images/2021/05/017-set-new-ssh-port.gif" class="kg-image" alt="How to Setup DigitalOcean Droplet Server"></figure><blockquote>We want to change the default. Using the default port 22 is a bad idea, as it is a common subject to hack attacks</blockquote><p></p><h4 id="update-ssh-configuration-file">Update SSH Configuration File </h4><p>All that remains now is to set the port to 13022 in the SSH config.</p><p>Open sshd_config:</p><pre><code class="language-Shell">sudo nano /etc/ssh/sshd_config</code></pre><p>Scroll down until you find <code>#Port 22</code> and change it to <code>Port 13022</code></p><p>Press <strong>CTRL+X to exit, </strong>and type Y and press enter to save.</p><figure class="kg-card kg-image-card"><img src="https://ivanskodje.com/content/images/2021/05/018-setting-new-ssh-port-config.gif" class="kg-image" alt="How to Setup DigitalOcean Droplet Server"></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://ivanskodje.com/content/images/2021/05/image-40.png" class="kg-image" alt="How to Setup DigitalOcean Droplet Server" srcset="https://ivanskodje.com/content/images/size/w600/2021/05/image-40.png 600w, https://ivanskodje.com/content/images/size/w1000/2021/05/image-40.png 1000w, https://ivanskodje.com/content/images/2021/05/image-40.png 1004w" sizes="(min-width: 720px) 720px"><figcaption>/etc/ssh/sshd_config</figcaption></figure><hr><p>Make sure you have the correct port in UFW:</p><pre><code class="language-Shell">sudo ufw status</code></pre><p>If you have the same port 13022/TCP there, you are good to continue.</p><hr><p>Restart SSHD, and exit:</p><pre><code class="language-Shell">sudo service sshd restart
exit</code></pre><figure class="kg-card kg-image-card"><img src="https://ivanskodje.com/content/images/2021/05/image-44.png" class="kg-image" alt="How to Setup DigitalOcean Droplet Server" srcset="https://ivanskodje.com/content/images/size/w600/2021/05/image-44.png 600w, https://ivanskodje.com/content/images/2021/05/image-44.png 943w" sizes="(min-width: 720px) 720px"></figure><p>If you try to connect without specifying a port, you will now get timed out:</p><figure class="kg-card kg-image-card"><img src="https://ivanskodje.com/content/images/2021/05/image-45.png" class="kg-image" alt="How to Setup DigitalOcean Droplet Server" srcset="https://ivanskodje.com/content/images/size/w600/2021/05/image-45.png 600w, https://ivanskodje.com/content/images/2021/05/image-45.png 931w" sizes="(min-width: 720px) 720px"></figure><p>Let us connect by specifying the port number we changed it to, 13022:</p><pre><code class="language-Shell">ssh &lt;username&gt;@&lt;domain&gt; -p 13022</code></pre><figure class="kg-card kg-image-card"><img src="https://ivanskodje.com/content/images/2021/05/020-login-with-port.gif" class="kg-image" alt="How to Setup DigitalOcean Droplet Server"></figure><p></p><p><strong>You are now done with the fundamental setup of a new DigitalOcean server, happy hacking!</strong></p>]]></content:encoded></item><item><title><![CDATA[Using Spring Aspect in Spring Boot]]></title><description><![CDATA[<p><strong>Spring Aspect (AOP) aims to help with separation of concerns by providing clean reusable annotation classes. </strong></p><!--kg-card-begin: html--><iframe width="560" height="315" src="https://www.youtube.com/embed/sE9NvCfa2LU" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe><!--kg-card-end: html--><p>When we work with REST controllers, especially when we create APIs for internal (or external) use, we usually end up with a lot of duplicate code that is difficult to refactor out. To solve</p>]]></description><link>https://ivanskodje.com/using-aop-in-spring-boot/</link><guid isPermaLink="false">5f7e0c1741f946000126ef59</guid><category><![CDATA[Java]]></category><dc:creator><![CDATA[Ivan Skodje]]></dc:creator><pubDate>Tue, 25 May 2021 14:36:22 GMT</pubDate><media:content url="https://ivanskodje.com/content/images/2021/05/background-2.png" medium="image"/><content:encoded><![CDATA[<img src="https://ivanskodje.com/content/images/2021/05/background-2.png" alt="Using Spring Aspect in Spring Boot"><p><strong>Spring Aspect (AOP) aims to help with separation of concerns by providing clean reusable annotation classes. </strong></p><!--kg-card-begin: html--><iframe width="560" height="315" src="https://www.youtube.com/embed/sE9NvCfa2LU" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe><!--kg-card-end: html--><p>When we work with REST controllers, especially when we create APIs for internal (or external) use, we usually end up with a lot of duplicate code that is difficult to refactor out. To solve this we will utilize Spring Aspect and Aspect-Oriented Programming (AOP) in a real world example of how you may completely <strong>remove duplicate code </strong>in your REST Controllers.</p><p>You will learn to setup your own annotation for your REST controllers, with the intention of removing a lot of duplicate code such as logging, method timing, and exception handling.</p><p>Using this, you can rewrite your REST API methods from looking like this: </p><pre><code class="language-Java">@GetMapping
public ResponseEntity&lt;List&lt;Client&gt;&gt; search(Client filter, Pageable pageable) {
    log.info("Starting GET in /api/client");
    long startTime = System.nanoTime();
    try {
        return CrudResource.search(filter, pageable, clientService);
    } catch (Problem problem) {
        throw problem;
    } catch (Exception ex) {
        log.error("Unhandled Exception: {}", ex.getMessage(), ex);
        throw new InternalServerErrorProblem(ex);
    } finally {
        double timeDifferenceInMs = (System.nanoTime() - startTime) / 1000000d;
        log.info("Time used in ms: {}", timeDifferenceInMs);
    }
}</code></pre><p>Into looking like this:</p><pre><code class="language-Java">@RestLog(uri = "/api/client")
@GetMapping
public ResponseEntity&lt;List&lt;Client&gt;&gt; search(Client filter, Pageable pageable) {
    return CrudResource.search(filter, pageable, clientService);
}</code></pre><p>The logging in both cases would look something similar to this:</p><pre><code class="language-log">15:39:12.472  INFO [] ClientResource    : Starting GET in /api/client
15:39:12.567  INFO [] ClientResource    : Time used in ms: 94.5073</code></pre><h2 id="perquisites">Perquisites</h2><ul><li>An existing spring boot project</li><li>A Rest Controller</li><li>Spring Aspect dependencies</li></ul><p>You will need to have an existing <strong>Spring Boot project</strong> up and running, along with a <strong>Rest Controller</strong>.  If you are using <code>spring-boot-starter-data-jpa</code> you should already have the aspect dependencies you need. </p><p>If you need to manually add it; you can add the <code>spring-boot-starter-aop</code> dependency. </p><pre><code class="language-xml">&lt;dependency&gt;
    &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
    &lt;artifactId&gt;spring-boot-starter-aop&lt;/artifactId&gt;
    &lt;scope&gt;compile&lt;/scope&gt;
&lt;/dependency&gt;</code></pre><h1 id="using-spring-aspect-in-spring-boot">Using Spring Aspect in Spring Boot</h1><ol><li>Create @RestLog annotation</li><li>Create RestLogAspect class</li><li>Move boilerplate code into RestLogAspect</li><li>Resulting Code</li></ol><h2 id="create-restlog-annotation">Create @RestLog annotation</h2><p>Create a new java interface with the name of the annotation you intend to create, which in our case is RestLog.java</p><pre><code class="language-Java">package com.ivanskodje.timehours.aop.rest;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RestLog {
    String uri() default "";
}
</code></pre><p>The <code>@Target</code> annotation is what allows you to specify where this annotation can be used. We will only allow this to be used on methods.</p><p><code>String uri() default "";</code> is an optional parameter value we will store the URI path that is logged in the original controller methods. There are better ways of handling this, but for the sake of this tutorial I will include different ways of getting data from the annotated method.</p><h2 id="create-restlogaspect-class">Create RestLogAspect class</h2><pre><code class="language-Java">package com.ivanskodje.timehours.aop.rest;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class RestLogAspect {

    @Around(value = "@annotation(restLogAnnotation)")
    public Object restLog(ProceedingJoinPoint joinPoint, RestLog restLogAnnotation) throws Throwable {
        return joinPoint.proceed();
    }
}
</code></pre><p>When we add the <code>@RestLog</code> annotation on a method, any calls to the method will have to go through the <code>@Around</code> method in our RestLogAspect class. This is what will allow us to log before and after, as well as handling any potential exceptions.</p><h2 id="move-boilerplate-code-into-restlogaspect">Move boilerplate code into RestLogAspect</h2><p>Looking at the original controller method, we see that the main purpose of this is to search for clients using a filter. </p><pre><code class="language-Java">@GetMapping
public ResponseEntity&lt;List&lt;Client&gt;&gt; search(Client filter, Pageable pageable) {
    log.info("Starting GET in /api/client");
    long startTime = System.nanoTime();
    try {
        return CrudResource.search(filter, pageable, clientService);
    } catch (Problem problem) {
        throw problem;
    } catch (Exception ex) {
        log.error("Unhandled Exception: {}", ex.getMessage(), ex);
        throw new InternalServerErrorProblem(ex);
    } finally {
        double timeDifferenceInMs = (System.nanoTime() - startTime) / 1000000d;
        log.info("Time used in ms: {}", timeDifferenceInMs);
    }
}</code></pre><p>The only code we <em>really </em>care about here is <code>return CrudResource.search(filter, pageable, clientService)</code>. Let us <strong>cut</strong> everything in the method, and paste it in our RestLogAspect class, with some minor modification.</p><pre><code class="language-Java">@RestLog(uri = "/api/client")
    @GetMapping
    public ResponseEntity&lt;List&lt;Client&gt;&gt; search(Client filter, Pageable pageable) {
        return CrudResource.search(filter, pageable, clientService);
    }</code></pre><pre><code class="language-Java">package com.ivanskodje.timehours.aop.rest;

import com.ivanskodje.timehours.aop.problem.InternalServerErrorProblem;
import com.ivanskodje.timehours.aop.problem.Problem;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class RestLogAspect {

    @Around(value = "@annotation(restLogAnnotation)")
    public Object restLog(ProceedingJoinPoint joinPoint, RestLog restLogAnnotation) throws Throwable {
        Logger log = LoggerFactory.getLogger(joinPoint.getTarget().getClass());
        log.info("Starting GET in /api/client");
        long startTime = System.nanoTime();
        try {
            return joinPoint.proceed();
        } catch (Problem problem) {
            throw problem;
        } catch (Exception ex) {
            log.error("Unhandled Exception: {}", ex.getMessage(), ex);
            throw new InternalServerErrorProblem(ex);
        } finally {
            double timeDifferenceInMs = (System.nanoTime() - startTime) / 1000000d;
            log.info("Time used in ms: {}", timeDifferenceInMs);
        }
    }
}
</code></pre><p>You can see that in place of the <code><code>return CrudResource.search(filter, pageable, clientService)</code></code> we are now using <code>return joinPoint.proceed()</code>. </p><p>In order to <code>log</code> as before, we are using the <code>joinPoint</code> to get the callers class <code>Logger log = LoggerFactory.getLogger(joinPoint.getTarget().getClass());</code>. Without this we would be logging as the <code>RestLogAspect</code> class itself, which would not be desirable. </p><p>If we now run our application, you will notice that it behaves identically as before. However, we are not yet using the <code>uri</code> parameter from the RestLog annotation, and we do not know if we are using this annotation on a GetMapping, PostMapping, DeleteMapping, (...), rest method. </p><pre><code class="language-Java">package com.ivanskodje.timehours.aop.rest;

import com.ivanskodje.timehours.aop.problem.InternalServerErrorProblem;
import com.ivanskodje.timehours.aop.problem.Problem;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.*;

import java.lang.reflect.Method;

@Aspect
@Component
public class RestLogAspect {

    @Around(value = "@annotation(restLogAnnotation)")
    public Object restLog(ProceedingJoinPoint joinPoint, RestLog restLogAnnotation) throws Throwable {
        long startTime = System.nanoTime();
        Logger log = LoggerFactory.getLogger(joinPoint.getTarget().getClass());
        String uri = restLogAnnotation.uri();
        String requestType = getRequestType(joinPoint);
        log.info("Starting {} in {}", requestType, uri);
        try {
            return joinPoint.proceed();
        } catch (Problem problem) {
            throw problem;
        } catch (Exception ex) {
            log.error("Unhandled Exception: {}", ex.getMessage(), ex);
            throw new InternalServerErrorProblem(ex);
        } finally {
            double timeDifferenceInMs = (System.nanoTime() - startTime) / 1000000d;
            log.info("Time used in ms: {}", timeDifferenceInMs);
        }
    }

    private String getRequestType(ProceedingJoinPoint joinPoint) {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();

        GetMapping getMapping = method.getAnnotation(GetMapping.class);
        if (getMapping != null) {
            return "GET";
        }

        DeleteMapping deleteMapping = method.getAnnotation(DeleteMapping.class);
        if (deleteMapping != null) {
            return "DELETE";
        }

        PostMapping postMapping = method.getAnnotation(PostMapping.class);
        if (postMapping != null) {
            return "POST";
        }

        PutMapping putMapping = method.getAnnotation(PutMapping.class);
        if (putMapping != null) {
            return "PUT";
        }

        PatchMapping patchMapping = method.getAnnotation(PatchMapping.class);
        if (patchMapping != null) {
            return "PATCH";
        }

        return "N/A";
    }
}
</code></pre><p><code>String uri = restLogAnnotation.uri()</code> is the straight and forward way of getting values that has been set on the annotation. </p><p><code>String requestType = getRequestType(joinPoint)</code> is using a new method that we created. From joinPoint we can check other annotations that is <strong>also </strong>set on the method we added <code>@RestLog</code> on. This allows is to easily verify whether or not we have a <code>XMapping</code> by attempting to get it. If it returns null, it means we don't have it on the method. Because the mapping annotations are mutually exclusive, this is a safe way of checking if we are on a for example a GET or POST method.</p><h2 id="resulting-code">Resulting Code</h2><h4 id="restlog-java">RestLog.java</h4><pre><code class="language-Java">package com.ivanskodje.timehours.aop.rest;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RestLog {
    String uri() default "";
}
</code></pre><h4 id="restlogaspect-java">RestLogAspect.java</h4><pre><code class="language-Java">package com.ivanskodje.timehours.aop.rest;

import com.ivanskodje.timehours.aop.problem.InternalServerErrorProblem;
import com.ivanskodje.timehours.aop.problem.Problem;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.*;

import java.lang.reflect.Method;

@Aspect
@Component
public class RestLogAspect {

    @Around(value = "@annotation(restLogAnnotation)")
    public Object restLog(ProceedingJoinPoint joinPoint, RestLog restLogAnnotation) throws Throwable {
        long startTime = System.nanoTime();
        Logger log = LoggerFactory.getLogger(joinPoint.getTarget().getClass());
        String uri = restLogAnnotation.uri();
        String requestType = getRequestType(joinPoint);
        log.info("Starting {} in {}", requestType, uri);
        try {
            return joinPoint.proceed();
        } catch (Problem problem) {
            throw problem;
        } catch (Exception ex) {
            log.error("Unhandled Exception: {}", ex.getMessage(), ex);
            throw new InternalServerErrorProblem(ex);
        } finally {
            double timeDifferenceInMs = (System.nanoTime() - startTime) / 1000000d;
            log.info("Time used in ms: {}", timeDifferenceInMs);
        }
    }

    private String getRequestType(ProceedingJoinPoint joinPoint) {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();

        GetMapping getMapping = method.getAnnotation(GetMapping.class);
        if (getMapping != null) {
            return "GET";
        }

        DeleteMapping deleteMapping = method.getAnnotation(DeleteMapping.class);
        if (deleteMapping != null) {
            return "DELETE";
        }

        PostMapping postMapping = method.getAnnotation(PostMapping.class);
        if (postMapping != null) {
            return "POST";
        }

        PutMapping putMapping = method.getAnnotation(PutMapping.class);
        if (putMapping != null) {
            return "PUT";
        }

        PatchMapping patchMapping = method.getAnnotation(PatchMapping.class);
        if (patchMapping != null) {
            return "PATCH";
        }

        return "N/A";
    }
}</code></pre><figure class="kg-card kg-code-card"><pre><code class="language-Java">package com.ivanskodje.timehours.api.crud;

import com.ivanskodje.timehours.aop.rest.RestLog;
import com.ivanskodje.timehours.api.crud.helper.CrudResource;
import com.ivanskodje.timehours.api.service.ClientService;
import com.ivanskodje.timehours.dal.client.Client;
import org.springframework.data.domain.Pageable;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
@RequestMapping("/api/client")
public class ClientResource {

    private final ClientService clientService;

    public ClientResource(ClientService clientService) {
        this.clientService = clientService;
    }

    @RestLog(uri = "/api/client")
    @GetMapping
    public ResponseEntity&lt;List&lt;Client&gt;&gt; search(Client filter, Pageable pageable) {
        return CrudResource.search(filter, pageable, clientService);
    }

    @RestLog(uri = "/api/client/{id}")
    @GetMapping(value = "/{id}")
    public ResponseEntity&lt;Client&gt; get(@PathVariable("id") Long id) {
        return CrudResource.get(id, clientService);
    }

    // omitted methods
    // ...
}
</code></pre><figcaption>The REST Controller&nbsp;</figcaption></figure>]]></content:encoded></item><item><title><![CDATA[Godot Engine Versioning (Video)]]></title><description><![CDATA[<p>Why did Godot Engine update to 3.3 instead of 4.0? Why did we update from 3.2.3 to 3.3.0, but not to 3.2.4? Let's talk about Godot Engine's versioning!</p><!--kg-card-begin: html--><iframe width="560" height="315" src="https://www.youtube.com/embed/BhPGuATbV8c" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe><!--kg-card-end: html-->]]></description><link>https://ivanskodje.com/godot-engine-versioning/</link><guid isPermaLink="false">60ad088041f946000126f82b</guid><category><![CDATA[Godot Engine]]></category><dc:creator><![CDATA[Ivan Skodje]]></dc:creator><pubDate>Fri, 14 May 2021 11:00:00 GMT</pubDate><media:content url="https://ivanskodje.com/content/images/2021/05/thumbnail.png" medium="image"/><content:encoded><![CDATA[<img src="https://ivanskodje.com/content/images/2021/05/thumbnail.png" alt="Godot Engine Versioning (Video)"><p>Why did Godot Engine update to 3.3 instead of 4.0? Why did we update from 3.2.3 to 3.3.0, but not to 3.2.4? Let's talk about Godot Engine's versioning!</p><!--kg-card-begin: html--><iframe width="560" height="315" src="https://www.youtube.com/embed/BhPGuATbV8c" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe><!--kg-card-end: html-->]]></content:encoded></item><item><title><![CDATA[Don't wait for Godot (Video)]]></title><description><![CDATA[<p>A short rant video about why you should not wait for Godot, and why I am not waiting.</p><!--kg-card-begin: html--><iframe width="560" height="315" src="https://www.youtube.com/embed/3uOB-tA4sC4" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe><!--kg-card-end: html-->]]></description><link>https://ivanskodje.com/dont-wait-for-godot/</link><guid isPermaLink="false">60ad095f41f946000126f83f</guid><category><![CDATA[Godot Engine]]></category><dc:creator><![CDATA[Ivan Skodje]]></dc:creator><pubDate>Wed, 28 Apr 2021 10:00:00 GMT</pubDate><media:content url="https://ivanskodje.com/content/images/2021/05/maxresdefault.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://ivanskodje.com/content/images/2021/05/maxresdefault.jpg" alt="Don't wait for Godot (Video)"><p>A short rant video about why you should not wait for Godot, and why I am not waiting.</p><!--kg-card-begin: html--><iframe width="560" height="315" src="https://www.youtube.com/embed/3uOB-tA4sC4" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe><!--kg-card-end: html-->]]></content:encoded></item><item><title><![CDATA[Setup a Secure Blog with Ghost on DigitalOcean]]></title><description><![CDATA[Tutorial for setting up a secure HTTPS blog running Ghost in a docker container, using Digitalocean as the provider.]]></description><link>https://ivanskodje.com/setup-a-secure-blog-with-ghost-on-digitalocean/</link><guid isPermaLink="false">5f272000b7f2ec00014f5901</guid><category><![CDATA[DevOps]]></category><dc:creator><![CDATA[Ivan Skodje]]></dc:creator><pubDate>Mon, 17 Aug 2020 13:52:00 GMT</pubDate><media:content url="https://ivanskodje.com/content/images/2020/08/image-10-1.png" medium="image"/><content:encoded><![CDATA[<img src="https://ivanskodje.com/content/images/2020/08/image-10-1.png" alt="Setup a Secure Blog with Ghost on DigitalOcean"><p></p><h3 id="video-tutorial">Video Tutorial</h3><!--kg-card-begin: html--><iframe width="560" height="315" src="https://www.youtube.com/embed/Dpp-UgcEAe0" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe><!--kg-card-end: html--><p></p><h3 id="prerequisites">Prerequisites</h3><p>In order to completely follow along this tutorial you will need the following.</p><ul><li>Access to DigitalOcean for setting up Droplets (VMs)</li><li>A domain name linked with DigitalOcean<br></li></ul><blockquote>For this tutorial, I will be using my own domain ivanskodje.com. Replace it with your own domain in the places I use it.</blockquote><h3 id="create-droplet">Create Droplet</h3><p>Create a new Droplet</p><figure class="kg-card kg-image-card"><img src="https://ivanskodje.com/content/images/2020/08/image.png" class="kg-image" alt="Setup a Secure Blog with Ghost on DigitalOcean"></figure><p>Since we already know we want a machine to setup Docker in, we can go to the Marketplace and find the appropriate Docker application.</p><figure class="kg-card kg-image-card"><img src="https://ivanskodje.com/content/images/2020/08/image-2.png" class="kg-image" alt="Setup a Secure Blog with Ghost on DigitalOcean" srcset="https://ivanskodje.com/content/images/size/w600/2020/08/image-2.png 600w, https://ivanskodje.com/content/images/size/w1000/2020/08/image-2.png 1000w, https://ivanskodje.com/content/images/2020/08/image-2.png 1228w" sizes="(min-width: 720px) 720px"></figure><p>Once that is done, it is up to you to choose the remainder droplet configuration. </p><p>I chose the most minimalistic and cost effective setup for this tutorial, but you are free to choose yours.</p><figure class="kg-card kg-image-card"><img src="https://ivanskodje.com/content/images/2020/08/image-5.png" class="kg-image" alt="Setup a Secure Blog with Ghost on DigitalOcean" srcset="https://ivanskodje.com/content/images/size/w600/2020/08/image-5.png 600w, https://ivanskodje.com/content/images/size/w1000/2020/08/image-5.png 1000w, https://ivanskodje.com/content/images/2020/08/image-5.png 1194w" sizes="(min-width: 720px) 720px"></figure><figure class="kg-card kg-image-card"><img src="https://ivanskodje.com/content/images/2020/08/image-6.png" class="kg-image" alt="Setup a Secure Blog with Ghost on DigitalOcean" srcset="https://ivanskodje.com/content/images/size/w600/2020/08/image-6.png 600w, https://ivanskodje.com/content/images/size/w1000/2020/08/image-6.png 1000w, https://ivanskodje.com/content/images/2020/08/image-6.png 1194w" sizes="(min-width: 720px) 720px"></figure><p></p><p><strong>Plan:</strong> Basic<br><strong>Cost:</strong> $5/mo<br><strong>Datacenter region:</strong> Amsterdam (3)<br><strong>Additional options:</strong> IPv6<br><strong>Authentication:</strong> Password<br><strong>Hostname:</strong> docker-nginx-letsencrypt-ghost<br><strong>Backups:</strong> No, but it is recommended to enable it.</p><h4 id="setup-our-domain-name">Setup our domain name</h4><p>While the droplet is being created we can move on to setting up our domain name. My domain is <em>ivanskodje.com</em>, so I intend to setup the sub domain my<em>ghost.ivanskodje.com</em>.</p><p>Select the domain name you intend to use.</p><figure class="kg-card kg-image-card"><img src="https://ivanskodje.com/content/images/2020/08/image-7.png" class="kg-image" alt="Setup a Secure Blog with Ghost on DigitalOcean" srcset="https://ivanskodje.com/content/images/size/w600/2020/08/image-7.png 600w, https://ivanskodje.com/content/images/size/w1000/2020/08/image-7.png 1000w, https://ivanskodje.com/content/images/2020/08/image-7.png 1200w" sizes="(min-width: 720px) 720px"></figure><p>We want to create two A records. One with hostname <strong>myghost</strong>, and the other with hostname <strong>www.myghost</strong>.</p><figure class="kg-card kg-image-card"><img src="https://ivanskodje.com/content/images/2020/08/image-8.png" class="kg-image" alt="Setup a Secure Blog with Ghost on DigitalOcean" srcset="https://ivanskodje.com/content/images/size/w600/2020/08/image-8.png 600w, https://ivanskodje.com/content/images/size/w1000/2020/08/image-8.png 1000w, https://ivanskodje.com/content/images/2020/08/image-8.png 1218w" sizes="(min-width: 720px) 720px"></figure><p>It may take a moment before the newly created record will take effect, so go ahead and copy the IP address before continuing.</p><h3 id="setup-new-user-disable-root">Setup new user &amp; disable root</h3><p>Now that we have created our droplet on Digitalocean, we want to disable root and setup our own user that we will use for the remainder of the tutorial.</p><p>In whatever operating system you are currently running in, open up your Terminal window and connect remotely to the droplet via ssh.</p><pre><code class="language-Bash">$ ssh root@YOUR-IP-HERE
</code></pre><h4 id="create-a-new-user">Create a new user</h4><p>Let us begin by creating our user which we will use to set everything up. During this tutorial I will be using the name "demouser". Answer the questions if you want.</p><pre><code class="language-Bash">$ adduser demouser</code></pre><p>In order for the newly user to have access to sudo commands, as well as docker commands, we want to add the user to <strong>docker</strong> and <strong>sudo</strong> groups.</p><pre><code>$ usermod -aG sudo,docker demouser</code></pre><p>Login as the new user.</p><pre><code>$ su - demouser</code></pre><p>Verify that we are in group <strong>sudo</strong> and <strong>docker.</strong></p><pre><code class="language-Bash">$ groups</code></pre><p>In addition to this, you can verify that you have access to the root folder.</p><pre><code class="language-Bash">$ sudo ls -al /root</code></pre><p>If you were successful, we can now safely disable the root user.</p><h4 id="disable-root-user">Disable root user</h4><p>Log out as the demouser by exiting once.</p><pre><code class="language-Bash">$ exit</code></pre><p>Then disable the login of the root user.</p><pre><code>$ passwd -l root</code></pre><p>If everything went well, it should tell you that the password expiry information changed. We will confirm this by attempting to reconnect as the root user. Exit once again in order to return to your  terminal.</p><pre><code class="language-Bash">$ exit</code></pre><p>Verify that you indeed cannot reconnect as the root user.</p><pre><code class="language-Bash">$ ssh root@YOUR-IP-HERE</code></pre><p>We expect to get a <strong>permission denied, please try again</strong> message. That is good. Let us reconnect using our newly created and tested user account.</p><pre><code class="language-Bash">$ ssh demouser@YOUR-IP-HERE</code></pre><blockquote>As a side note, there are other more formal ways of disabling remote access to the root user. <br>When setting up a server, it is also a good idea to change the default ssh port.</blockquote><p>We are now ready to continue.</p><h3 id="install-nginx">Install Nginx</h3><p>Install it.</p><pre><code class="language-Bash">$ sudo apt update 
$ sudo apt install nginx</code></pre><h3 id="open-ports">Open ports</h3><p>You can check which ports are open.</p><pre><code class="language-Bash">$ sudo ufw status</code></pre><p>By default, port 80 (http) and 443 (https) is not open. Let us open it.</p><pre><code class="language-Bash">$ sudo ufw allow 80/tcp
$ sudo ufw allow 443/tcp</code></pre><p>Confirm the ports are open by checking the status again.</p><pre><code class="language-Bash">$ sudo ufw status</code></pre><p>Good job.</p><p>You can verify that you are getting the default nginx entry page when you enter your web address in the browser. It should look like this. </p><figure class="kg-card kg-image-card"><img src="https://ivanskodje.com/content/images/2020/08/image-9.png" class="kg-image" alt="Setup a Secure Blog with Ghost on DigitalOcean"></figure><p>This means nginx is working as expected. We are ready to setup the domain name we want to use.</p><h4 id="setup-the-domain-virtual-name-in-nginx">Setup the domain (virtual name) in Nginx</h4><p>If you navigate to /etc/nginx/sites-available, you can see we have a <strong>default</strong> file. That is the file that nginx currently serves you when you view your page in the browser. </p><pre><code class="language-Bash">$ cd /etc/nginx/sites-available
$ ls
default</code></pre><p>We want to make a new file to tell nginx that we want to use myghost.ivanskodje.com. </p><pre><code class="language-Bash">sudo nano myghost.ivanskodje.com</code></pre><p>Then enter the following information. </p><pre><code class="language-Text">server {
	listen 80;
	listen [::]:80;
	
	server_name myghost.ivanskodje.com www.myghost.ivanskodje.com;
    
    	root /var/www/myghost.ivanskodje.com;
    
        location / {
            try_files $uri $uri/ =404;
        }
}</code></pre><p>When that is done, let us make a shortcut to tell nginx that we want to use this. It is important to use the full path.</p><pre><code class="language-Bash">$ sudo ln -s /etc/nginx/sites-available/myghost.ivanskodje.com /etc/nginx/sites-enabled/</code></pre><p>Lastly before we restart nginx, we want to create a new folder and file to serve when we enter myghost.ivanskodje.com in the browser.</p><pre><code class="language-Bash">$ sudo mkdir /var/www/myghost.ivanskodje.com</code></pre><p>Create index.html.</p><pre><code class="language-Bash">$ sudo nano /var/www/myghost.ivanskodje.com/index.html</code></pre><p>Creating a minimal html page.</p><pre><code class="language-Text">&lt;html&gt;
&lt;body&gt;

&lt;h1&gt;Hello&lt;/h1&gt;

&lt;/body&gt;
&lt;/html&gt;</code></pre><p>Everything should now be ready, let us restart Nginx to make the changes take effect.</p><pre><code class="language-Bash">$ sudo service nginx restart</code></pre><p>Navigate to <strong>myghost.ivanskodje.com</strong> to verify that you see a big Hello.</p><p>With most of the heavy preparations in place, let us move on by securing it. </p><h3 id="install-certbot-to-setup-free-https-certificates">Install Certbot to setup free HTTPS certificates</h3><blockquote>If you are having trouble setting up Certbot, you may find further instructions on their <a href="https://certbot.eff.org/instructions">web-site</a>.</blockquote><p>Download certbot</p><pre><code class="language-Bash">$ sudo snap certbot --classic</code></pre><p>Secure our domain(s) with certbot</p><pre><code class="language-Bash">$ sudo certbot --nginx</code></pre><p>It will ask you two questions you have to answer. First question you have to accept by entering A. The second question is whether or not you want to opt in sharing your email address. </p><p>Select your www.myghost and myghost domain by entering the two numbers as described, separated by comma (Probably `1,2`)</p><p>Assuming everything went well, it should now have secured your web-site. Go back to the browser to confirm that you indeed are getting redirected to a HTTPS page. </p><h3 id="install-ghost-with-docker">Install Ghost with Docker</h3><p>With all the work done, this is the easiest part. In your home folder (or any folder of your choosing), create a new file.</p><pre><code class="language-Bash">$ cd ~
$ nano run-ghost.sh</code></pre><p>Then enter the following.</p><pre><code class="language-Bash">#!/bin/sh

# Remove the existing container
docker rm -f ghost-myghost.ivanskodje.com

# Create new container (using volume for data persistence)
docker run --name ghost-myghost.ivanskodje.com \
-p 127.0.0.1:2368:2368 \
--restart=always \
-e url=https://myghost.ivanskodje.com \
-v /home/$USER/ghost-myghost.ivanskodje.com/content:/var/lib/ghost/content \
-d \
ghost:3-alpine
</code></pre><p>Finally we need to make the file executable, in order to run it.</p><pre><code class="language-Bash">$ chown +x run-ghost.sh</code></pre><p>Then run it.</p><pre><code>$ ./run-ghost.sh</code></pre><p>Verify that the newly created container is running.</p><pre><code class="language-Bash">$ docker ps</code></pre><h6 id="explanation">Explanation</h6><blockquote>docker rm -f ghost-myghost.ivanskodje.com</blockquote><p>The first time we run this script, this will do nothing. It simply allows us to "restart" the container, by removing any existing container before we create a new one.</p><blockquote>docker run --name ghost-myghost.ivanskodje.com \</blockquote><p>Starts a container with the name "ghost-myghost.ivanskodje.com". You can choose your own name.</p><blockquote>-p 127.0.0.1:2368:2368 \</blockquote><p>Tells us which ports to use.</p><blockquote>--restart=always \</blockquote><p>If something goes wrong, we will attempt to restart the container.</p><blockquote>-e url=https://myghost.ivanskodje.com \</blockquote><p>This is used by ghost to setup the root url. Without this setup you will have a bad time.</p><blockquote>-v /home/$USER/ghost-myghost.ivanskodje.com/content:/var/lib/ghost/content \</blockquote><p>This is the most important part. This is what makes our ghost container persistent. It will store the important data in our own home folder, in the folder /ghost-myghost.ivanskodje.com/content. You are free to change this, just make sure you leave the right side unchanged (/your/path/here:/var/lib/ghost/content).</p><blockquote>-d \</blockquote><p>Run container detached. I assume you want no commitment to this container, so it is better to remain detached. </p><blockquote>ghost:3-alpine</blockquote><p>The last line tells us which image to use. You can find other ghost images, but I chose this one, since it is more lightweight. </p><h3 id="redirect-nginx-to-the-ghost-container">Redirect Nginx to the Ghost container</h3><p>Navigate back to the nginx folder. We will edit the myghost.ivanskodje.com file in order to redirect it to the ghost container.</p><pre><code class="language-Bash">$ cd /etc/nginx/sites-available
$ sudo nano myghost.ivanskodje.com</code></pre><p>You may notice that it has changed a bit since last time. This is because letsencrypt automatically made the changes needed to redirect from http to https. Luckily we only want to make a small change. We want to change the location we are pointing at. <br>Remove the root and alter the location. Leave the rest alone,  as it is handled by Certbot. </p><pre><code class="language-Text">server {
	server_name myghost.ivanskodje.com www.myghost.ivanskodje.com;
        
    location / {
    	proxy_pass http://127.0.0.1:2368;
        proxy_set_header Host $http_host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto https;
    }
    
    # content left out here... managed by Certbot
}

server {
 # content left out here... managed by Certbot
}</code></pre><p>Do a final reload of Nginx.</p><pre><code class="language-Bash">$ sudo service nginx reload</code></pre><p>That should be it. When you go in the browser, and navigate to myghost.ivanskodje.com, you should see your Ghost blog.</p><figure class="kg-card kg-image-card"><img src="https://ivanskodje.com/content/images/2020/08/image-10.png" class="kg-image" alt="Setup a Secure Blog with Ghost on DigitalOcean" srcset="https://ivanskodje.com/content/images/size/w600/2020/08/image-10.png 600w, https://ivanskodje.com/content/images/2020/08/image-10.png 837w" sizes="(min-width: 720px) 720px"></figure><p>You can setup ghost further by adding <strong>/ghost</strong> at the end of your address. In my case the full address would be <strong>https://myghost.ivanskodje.com/ghost</strong>.</p><p>Thank you for reading.</p>]]></content:encoded></item><item><title><![CDATA[How to create DigitalOcean droplets using Doctl on Windows]]></title><description><![CDATA[<p>In this guide I will be walking through the process of setting up a droplet from your own windows computer, using DigitalOcean's API tool <strong>Doctl</strong>.</p><p>Knowing how to do this may help you automate certain processes such as setting up testing environments or servers on demand.</p><h1 id="download-doctl">Download doctl</h1><p>Go to</p>]]></description><link>https://ivanskodje.com/how-to-create-digitalocean-droplets-using-doctl-on-windows/</link><guid isPermaLink="false">60ace68841f946000126f7d5</guid><category><![CDATA[DevOps]]></category><dc:creator><![CDATA[Ivan Skodje]]></dc:creator><pubDate>Thu, 06 Feb 2020 11:08:00 GMT</pubDate><media:content url="https://ivanskodje.com/content/images/2021/05/header-1.png" medium="image"/><content:encoded><![CDATA[<img src="https://ivanskodje.com/content/images/2021/05/header-1.png" alt="How to create DigitalOcean droplets using Doctl on Windows"><p>In this guide I will be walking through the process of setting up a droplet from your own windows computer, using DigitalOcean's API tool <strong>Doctl</strong>.</p><p>Knowing how to do this may help you automate certain processes such as setting up testing environments or servers on demand.</p><h1 id="download-doctl">Download doctl</h1><p>Go to doctl's <a href="https://github.com/digitalocean/doctl">official github</a> page.<br></p><figure class="kg-card kg-image-card"><img src="https://ivanskodje.com/content/images/2021/05/image.png" class="kg-image" alt="How to create DigitalOcean droplets using Doctl on Windows" srcset="https://ivanskodje.com/content/images/size/w600/2021/05/image.png 600w, https://ivanskodje.com/content/images/size/w1000/2021/05/image.png 1000w, https://ivanskodje.com/content/images/2021/05/image.png 1287w" sizes="(min-width: 720px) 720px"></figure><p>Select the <strong>releases</strong> tab.<br></p><figure class="kg-card kg-image-card"><img src="https://ivanskodje.com/content/images/2021/05/image-1.png" class="kg-image" alt="How to create DigitalOcean droplets using Doctl on Windows" srcset="https://ivanskodje.com/content/images/size/w600/2021/05/image-1.png 600w, https://ivanskodje.com/content/images/size/w1000/2021/05/image-1.png 1000w, https://ivanskodje.com/content/images/2021/05/image-1.png 1278w" sizes="(min-width: 720px) 720px"></figure><p>We will have to choose the windows version that match our own CPU.<br>If you are running on a computer with an AMD CPU (64bit), select <strong>doct-1.8.3-windows-4.0-amd64.zip</strong> (or whichever version is current).<br>If you are running on an intel computer (32 or 64bit), select <strong>doctl-1.8.3-windows-4.0-386.zip</strong> (or whichever version is current).</p><h3 id="do-i-use-intel-or-amd">Do I use Intel or AMD?</h3><p>If you do not know whether you are running on an AMD or Intel CPU, open up the start menu and enter <strong>dxdiag</strong>.</p><figure class="kg-card kg-image-card"><img src="https://ivanskodje.com/content/images/2021/05/image-4.png" class="kg-image" alt="How to create DigitalOcean droplets using Doctl on Windows"></figure><p><br>The first time you run it, it is going to ask if you would like to check if your drivers are digitally signed. Confirm it.</p><p>Here you should be able to see whether or not it says AMD or Intel(R) next to <strong>Processor</strong>. In this example I use an Intel CPU.</p><figure class="kg-card kg-image-card"><img src="https://ivanskodje.com/content/images/2021/05/image-5.png" class="kg-image" alt="How to create DigitalOcean droplets using Doctl on Windows" srcset="https://ivanskodje.com/content/images/size/w600/2021/05/image-5.png 600w, https://ivanskodje.com/content/images/2021/05/image-5.png 723w" sizes="(min-width: 720px) 720px"></figure><h1 id="install-and-setup-doctl">Install and setup doctl</h1><p>After you have downloaded the appropriate zip file, extract the contents to a folder of your choice. I <em>would recommend</em> you rename it and put it under C:/doctl/ for ease of access. Whichever path you choose, make sure you keep using your own path throughout the examples.</p><p>Next we want to make sure we can access the doctl.exe file from any location via the command line or powershell by adding it as a <strong>system path environment variable</strong>.</p><hr><h3 id="how-to-setup-the-system-environment-variable">How to setup the System Environment Variable</h3><p>1. Via the Start Menu, search and open <strong>Edit the system environment variables</strong>.</p><p>2. In the <strong>Advanced</strong> tab, press the <strong>Environment Variables…</strong> button. </p><figure class="kg-card kg-image-card"><img src="https://ivanskodje.com/content/images/2021/05/image-10.png" class="kg-image" alt="How to create DigitalOcean droplets using Doctl on Windows"></figure><p>3. Double-click the <strong>Path</strong> variable in order to open it.</p><p>4. Select <strong>New</strong> and enter the path to the folder: <code>C:\doctl</code></p><p>5. Reopen powershell or terminal if i already was open in order for the changes to take effect.</p><h2 id="getting-your-api-token-from-digitalocean-com">Getting your API Token from Digitalocean.com</h2><p>An API token is a randomly generated value generated by digitalocean, that will act as your secure password and ID for interacting with the digitalocean API via doctl.</p><p>Start by logging in to your <a href="https://www.digitalocean.com/">DigitalOcean</a> account, and go to your API page.</p><figure class="kg-card kg-image-card"><img src="https://ivanskodje.com/content/images/2021/05/image-11.png" class="kg-image" alt="How to create DigitalOcean droplets using Doctl on Windows" srcset="https://ivanskodje.com/content/images/size/w600/2021/05/image-11.png 600w, https://ivanskodje.com/content/images/2021/05/image-11.png 635w"></figure><p>Make sure you are in the <strong>Tokens/Keys</strong> tab and select the huge <strong>Generate New Token</strong> button in the middle.</p><figure class="kg-card kg-image-card"><img src="https://ivanskodje.com/content/images/2021/05/image-12.png" class="kg-image" alt="How to create DigitalOcean droplets using Doctl on Windows" srcset="https://ivanskodje.com/content/images/size/w600/2021/05/image-12.png 600w, https://ivanskodje.com/content/images/size/w1000/2021/05/image-12.png 1000w, https://ivanskodje.com/content/images/2021/05/image-12.png 1034w" sizes="(min-width: 720px) 720px"></figure><p>A popup will show up, and ask you for a Token name, as well as whether or not to give it Write access. Leave it as is, and give it a recognizable and <strong>nice token name</strong>. Select <strong>Generate Token</strong>.</p><figure class="kg-card kg-image-card"><img src="https://ivanskodje.com/content/images/2021/05/image-13.png" class="kg-image" alt="How to create DigitalOcean droplets using Doctl on Windows"></figure><p>A new entry should have been added, and below the nice token name, you should see a long string.</p><figure class="kg-card kg-image-card"><img src="https://ivanskodje.com/content/images/2021/05/image-14.png" class="kg-image" alt="How to create DigitalOcean droplets using Doctl on Windows" srcset="https://ivanskodje.com/content/images/size/w600/2021/05/image-14.png 600w, https://ivanskodje.com/content/images/size/w1000/2021/05/image-14.png 1000w, https://ivanskodje.com/content/images/2021/05/image-14.png 1025w" sizes="(min-width: 720px) 720px"></figure><p>Copy the string, either by doing it manually or pressing the invisible Copy button to the right of it. <strong>Make sure you store it somewhere secure.</strong></p><p>Now we have our token to use with doctl.</p><h2 id="authenticate-yourself-in-doctl">Authenticate yourself in Doctl</h2><p>Open up your favorite command prompt and enter:</p><pre><code>doctl auth init
</code></pre><figure class="kg-card kg-image-card"><img src="https://ivanskodje.com/content/images/2021/05/image-15.png" class="kg-image" alt="How to create DigitalOcean droplets using Doctl on Windows" srcset="https://ivanskodje.com/content/images/size/w600/2021/05/image-15.png 600w, https://ivanskodje.com/content/images/2021/05/image-15.png 778w" sizes="(min-width: 720px) 720px"></figure><p>It will then ask for your digitalocean access token.<br>Make sure you do not have any trailing white spaces when copy-pasting the token from the previous section.</p><p>We are now ready to enter the appropriate commands to create our very own droplets from the command line.</p><h1 id="creating-the-droplet">Creating the Droplet</h1><p>Before we can create a droplet, we need to figure out where we want to host it, what type of image (OS) we want to run it on, and the size of the droplet.</p><p>The pattern of the command we are going to use looks like this:</p><pre><code>doctl compute droplet create &lt;name&gt; --region &lt;region-slug&gt; --image &lt;image-slug&gt; --size &lt;size-slug&gt;
</code></pre><h2 id="picking-a-region">Picking a region</h2><p>Let us begin by figuring out which region we want to host the droplet in. We can get a list of available regions by typing in the following command:</p><pre><code>doctl compute region list
</code></pre><figure class="kg-card kg-image-card"><img src="https://ivanskodje.com/content/images/2021/05/image-16.png" class="kg-image" alt="How to create DigitalOcean droplets using Doctl on Windows" srcset="https://ivanskodje.com/content/images/size/w600/2021/05/image-16.png 600w, https://ivanskodje.com/content/images/2021/05/image-16.png 777w" sizes="(min-width: 720px) 720px"></figure><p>Here you can choose the slug for the region you wish to use. For this example, I will pick "London 1", which has the slug name of <code>lon1</code>.</p><h2 id="picking-an-distribution-application-image">Picking an distribution / application image</h2><p>We can choose whether or not we want use a clean distribution image, or a ready-to-go application image such as Docker, GitLab, LEMP, and so on.</p><p>You can list the available <strong>distribution</strong> images by entering:</p><pre><code>doctl compute image list-distribution
</code></pre><figure class="kg-card kg-image-card"><img src="https://ivanskodje.com/content/images/2021/05/image-17.png" class="kg-image" alt="How to create DigitalOcean droplets using Doctl on Windows" srcset="https://ivanskodje.com/content/images/size/w600/2021/05/image-17.png 600w, https://ivanskodje.com/content/images/2021/05/image-17.png 862w" sizes="(min-width: 720px) 720px"></figure><p>You can list the available <strong>application</strong> images by entering:</p><pre><code>doctl compute image list-application
</code></pre><figure class="kg-card kg-image-card"><img src="https://ivanskodje.com/content/images/2021/05/image-18.png" class="kg-image" alt="How to create DigitalOcean droplets using Doctl on Windows" srcset="https://ivanskodje.com/content/images/size/w600/2021/05/image-18.png 600w, https://ivanskodje.com/content/images/2021/05/image-18.png 896w" sizes="(min-width: 720px) 720px"></figure><p>As we did with the region, choose the appropriate slug matching the image of your choice. For this example I will use the CentOS distribution image, which has the slug name <strong>centos-7-x64</strong>.</p><h2 id="choosing-a-droplet-size">Choosing a droplet size</h2><p>In order to list available sizes, we can run:</p><pre><code>doctl compute size list
</code></pre><figure class="kg-card kg-image-card"><img src="https://ivanskodje.com/content/images/2021/05/image-19.png" class="kg-image" alt="How to create DigitalOcean droplets using Doctl on Windows" srcset="https://ivanskodje.com/content/images/size/w600/2021/05/image-19.png 600w, https://ivanskodje.com/content/images/2021/05/image-19.png 647w"></figure><p>I will choose the cheapest one with a slug name <strong>s-1vcpu-1gb</strong>.</p><h2 id="creating-the-droplet-1">Creating the droplet</h2><p>Now that we have our slug for region, image, and size, we can create our image from the command line.</p><blockquote>region: lon1<br>image: centos-7-x64<br>size: s-1vcpu-1gb</blockquote><p>Choose a name for the droplet, and fill in the blanks.</p><pre><code>doctl compute droplet create &lt;name&gt; --region &lt;region-slug&gt; --image &lt;image-slug&gt; --size &lt;size-slug&gt;
</code></pre><p>I will name my droplet <strong>my-droplet</strong> and fill in the rest. This gives me a full command that looks like this:</p><pre><code>doctl compute droplet create my-droplet --region lon1 --image centos-7-x64 --size s-1vcpu-1gb
</code></pre><figure class="kg-card kg-image-card kg-width-wide"><img src="https://ivanskodje.com/content/images/2021/05/image-20.png" class="kg-image" alt="How to create DigitalOcean droplets using Doctl on Windows" srcset="https://ivanskodje.com/content/images/size/w600/2021/05/image-20.png 600w, https://ivanskodje.com/content/images/size/w1000/2021/05/image-20.png 1000w, https://ivanskodje.com/content/images/2021/05/image-20.png 1400w" sizes="(min-width: 1200px) 1200px"></figure><p>The login information for your droplet should be sent to your email associated with your DigitalOcean account.<br>You should now be running your very own Droplet, created from command line using Doctl.</p><p>Visit your <a href="https://cloud.digitalocean.com/dashboard">DigitalOcean dashboard</a>, under the resources tab, in order to view your new Droplet information.</p><figure class="kg-card kg-image-card"><img src="https://ivanskodje.com/content/images/2021/05/image-21.png" class="kg-image" alt="How to create DigitalOcean droplets using Doctl on Windows" srcset="https://ivanskodje.com/content/images/size/w600/2021/05/image-21.png 600w, https://ivanskodje.com/content/images/2021/05/image-21.png 797w" sizes="(min-width: 720px) 720px"></figure>]]></content:encoded></item><item><title><![CDATA[Don't Think, Shortening Variable Names]]></title><description><![CDATA[<p><strong>Shortening variable names is a huge waste of time.</strong></p><!--kg-card-begin: html--><iframe width="560" height="315" src="https://www.youtube.com/embed/AkeYwaV7s-Q" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe><!--kg-card-end: html--><pre><code class="language-java">if(ts.has(t)) {
    ts.close();
}
</code></pre><p>TS. Tidy Storage. Tennant Summit. The Store. Team Session. What is <code>ts</code>? What is <code>t</code>?<br>Looking at the code closer, we discover the following.</p><pre><code class="language-java">TrainStation ts = city.getClosestTs();
</code></pre><p>Aha, ts is a train station, of</p>]]></description><link>https://ivanskodje.com/dont-think-shortening-variable-names/</link><guid isPermaLink="false">60ace5b441f946000126f7bc</guid><category><![CDATA[Clean Coding]]></category><dc:creator><![CDATA[Ivan Skodje]]></dc:creator><pubDate>Fri, 03 Jan 2020 17:15:00 GMT</pubDate><media:content url="https://ivanskodje.com/content/images/2021/05/think-1.png" medium="image"/><content:encoded><![CDATA[<img src="https://ivanskodje.com/content/images/2021/05/think-1.png" alt="Don't Think, Shortening Variable Names"><p><strong>Shortening variable names is a huge waste of time.</strong></p><!--kg-card-begin: html--><iframe width="560" height="315" src="https://www.youtube.com/embed/AkeYwaV7s-Q" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe><!--kg-card-end: html--><pre><code class="language-java">if(ts.has(t)) {
    ts.close();
}
</code></pre><p>TS. Tidy Storage. Tennant Summit. The Store. Team Session. What is <code>ts</code>? What is <code>t</code>?<br>Looking at the code closer, we discover the following.</p><pre><code class="language-java">TrainStation ts = city.getClosestTs();
</code></pre><p>Aha, ts is a train station, of course - that makes sense!<br>Okay. Let's go back to the code again.</p><pre><code class="language-java">if(ts.has(t)) {
    ts.close();
}
</code></pre><p>Could that mean the <code>t</code> is a train? Probably, but we would have to check to be sure. Perhaps it uses a custom implementation of Train <code>Train t = new ElectricTrain()</code>. However, perhaps they should have used <code>et</code> instead of <code>t</code>.</p><h2 id="advantages-shortening-variables">Advantages shortening variables</h2><p>Let us look at the advantages of shortened variables.</p><ol><li>You save horizontal screen space.</li></ol><p>A very disappointing list of one. However, some may claim the following:</p><ul><li>"You save time spending less time typing!"</li><li>"You will code faster!"</li></ul><p>And to that I say, <strong>BALONEY!</strong></p><h2 id="are-shortened-variables-a-waste-of-time">Are shortened variables a waste of time?</h2><p>Believe it or not, shortening variables is a big time waster. Not only are you wasting your own time, you are also wasting everyone else's time.<br>The reason for this is surprisingly intuitive once you stop to think about it.</p><h3 id="workflow-using-shortened-variable-names">Workflow using shortened variable names</h3><p>This is assuming you are not very familiar with the code. If you are very familiar with the code, you can skip step 2 and 4, since you already have that knowledge somewhere in your memory.</p><ol><li>You read the code</li><li>You need to find out what <code>ts</code> is.</li><li>Keep a mental map that <code>ts</code> is TrainStation</li><li>Find out what <code>t</code> is.</li><li>Keep a mental map that <code>t</code> is Train</li><li>Make changes to code</li><li>Always remember that <code>ts</code> is TrainStation, and <code>t</code> is Train.</li></ol><h3 id="workflow-using-long-descriptive-names">Workflow using long descriptive names</h3><ol><li>You read the code</li><li>Make changes to code</li></ol><p>Not a lot of steps involved when you cut the shortened variables.</p><h2 id="keeping-a-mental-map">Keeping a Mental Map</h2><figure class="kg-card kg-image-card"><img src="https://blogg.itverket.no/content/images/2019/11/think.png" class="kg-image" alt="Don't Think, Shortening Variable Names"></figure><p>In your mind, perceiving the variable name "hello" is <strong>faster</strong> than "h".<br>When you look at "h" you have to recall that "h" means "hello", every single time. When I say recall, I don't mean that you need to stop to figure out that "h" means "hello". You obviously know that, but you still have to form a mental link between "h" and "hello". This takes a little time that gives your mind more work.</p><p>Imagine how many times you have to do this. The time you claim to <em>save</em> typing "h" is lost countless times over with the additional time you spend mapping these variables.<br>You might even say that the act of shortening variables are selfish. Remember you are also wasting the time of everyone else reading the code.</p><p>This is not the worst part. It is even more costly when you jump between code context. "h" in our example might represent "heavy" in another class.<br>The time it takes to absorb a new context with multiple variables that are shortened is more than likely much greater than the time you waste working with only one shortened variable.</p><h1 id="in-summary">In Summary</h1><p>Shortening variables add several unnecessary and wasteful steps in your mental workflow. Not only is it an inefficient way to code, you are also wasting the time of your entire team.<br>In contrast, using longer descriptive variable names provides the user with the information then and there, without the need to look it up.</p><p><strong>Don't make the developers have to think more than they already do!</strong></p><h3 id="let-me-finish-this-off-with-a-relevant-quote-">Let me finish this off with a relevant quote:</h3><p><em>"Indeed, the ratio of time spent reading versus writing is well over 10 to 1. We are constantly reading old code as part of the effort to write new code. ...[Therefore,] making it easy to read makes it easier to write."</em> - Robert C. Martin (Clean Code: A Handbook of Agile Software Craftsmanship)</p>]]></content:encoded></item><item><title><![CDATA[Are Developers Afraid of Smaller Classes?]]></title><description><![CDATA[<p>Smaller classes seem to make a lot of developers uncomfortable.</p><p>It seems that the idea of jumping between classes in order to get <em>the whole picture</em> is undesirable, and that instead they would prefer a gigantic class with 20-30 methods, over 5-10 classes with 2-3 methods.<br>What is it about</p>]]></description><link>https://ivanskodje.com/are-developers-afraid-of-smaller-classes/</link><guid isPermaLink="false">60ace57541f946000126f7b1</guid><category><![CDATA[Clean Coding]]></category><dc:creator><![CDATA[Ivan Skodje]]></dc:creator><pubDate>Sat, 30 Nov 2019 14:09:00 GMT</pubDate><media:content url="https://ivanskodje.com/content/images/2021/05/dinovschick-1.png" medium="image"/><content:encoded><![CDATA[<img src="https://ivanskodje.com/content/images/2021/05/dinovschick-1.png" alt="Are Developers Afraid of Smaller Classes?"><p>Smaller classes seem to make a lot of developers uncomfortable.</p><p>It seems that the idea of jumping between classes in order to get <em>the whole picture</em> is undesirable, and that instead they would prefer a gigantic class with 20-30 methods, over 5-10 classes with 2-3 methods.<br>What is it about using smaller classes that make people uncomfortable? Does not smaller classes with clear purposes make the code more maintainable? Does it not make it more testable? Does it not make the code short, readable, and concise?</p><h2 id="chicken-vs-dinosaurs">Chicken vs Dinosaurs</h2><p>Would you rather fight 100 small dinosaurs, or a giant chicken?<br></p><figure class="kg-card kg-image-card"><img src="https://blogg.itverket.no/content/images/2019/11/dinovschick.png" class="kg-image" alt="Are Developers Afraid of Smaller Classes?"></figure><p>We have all had this discussion at some point. Perhaps the giant chicken was a giant horse. Or perhaps the dinosaurs were tiny horses, I don't know.<br>Regardless, I would rather choose to face 100 small dinosaurs. A single attack from the chicken would probably critically injure or terminate us, but with the 100 small dinosaurs we would have a fighting chance. You cannot outrun a giant chicken, after all!</p><h2 id="smaller-classes">Smaller Classes</h2><p><strong>I believe the same applies to classes!</strong><br>We should split the big classes into smaller dinosaur classes, so that we can tackle one small problem at a time. Let each little class get their time to shine when we get change requests, or when adding new functionality.</p><h3 id="advantages-with-smaller-classes">Advantages with Smaller Classes</h3><ul><li>Maintainability</li><li>Flexibility</li><li>Readability</li></ul><p>These three key points are some of the most important goals for us, the developers, to follow.</p><p><strong>Maintainability</strong> is provided by separating certain behavior out of the larger classes; into a class whose prime responsibility is to service that behavior to other class(es). When we need to make a change, we only need to change it there, and only <strong>once</strong>.</p><p><strong>Flexibility</strong> comes from being able to use existing work. We can extend the separated class to override certain functionality, or perhaps build a wrapper class that takes in the class as a parameter. Perhaps the proper course of action would be to convert the class we are working on into an interface, and work with that.</p><p><strong>Readability</strong> is shown by reading the class. Having a concise class with a single behavior/purpose allows us to look at that class, and read what that class is doing. It allows us to more easily read the code, by not polluting your classes with logic of certain behavior that really belongs in its own class.</p><blockquote>Working with big classes is like fighting a giant chicken. Messy, and difficult to work with.</blockquote><h2 id="code-culture">Code Culture</h2><p>There seem to be an ongoing trend that many of us (developers) shy away from splitting up potentially big classes into smaller classes. Worse yet, there may be circumstances when we simply keep adding more and more into the same class.</p><p>The <em>"best"</em> and common argument I've heard for having larger classes, is having access to all the code in one place. You can easily find all the code related to the class, at least in the project tree. Because you are already there.<br>However, this is also likely to be a most unfortunate outcome. This kind of thinking breeds further technical debt and complexity into our code. Making the junior  developers lose sleep and hair over it. Why? Because you would <strong>have</strong> to become familiar with the entire class in order to put together what is going on. If you had separated responsibilities into smaller classes, you would almost instantaneous recognize the behavior of the class you are in.</p><p>Unfortunately, the developers with a few years under their belt that are already familiar with the code, seem to be the biggest opponents of change. After all, they are <strong>already</strong> familiar with the code. They have overcome any maintainability and readability issues by spending the time developing and maintaining the mess. If someone were to clean up the code, they would have to re-familiarize themselves, and that can unfortunately be <strong>uncomfortable</strong>.</p><h2 id="facing-your-fears">Facing your Fears</h2><p>How would we go about to make change?<br>Dedicate a portion of your time refactoring and cleaning up the code you are working on! Make sure good tests are in place to protect you from making mistakes, and don't you go overboard by redesigning the entire system without the cooperation of your team architect. After all, your job is to make sure the code is maintainable, readable and flexible to change.</p><blockquote>Test Driven Development is a good strategy to allow further change while reducing risk considerably</blockquote><p>If you are unable to dedicate extra time for technical debt, for whatever reasons, you- together with the developers in your team would greatly benefit from adding additional time to your task estimations.<br>You are responsible for the code you write, and making sure you write code with quality is part of your job as a professional developer.</p><hr><h3 id="tips-for-further-study">Tips for further Study</h3><p>Writing <em>clean code</em> is not easy, and the road towards achieving it is long. It is very unlikely you will ever get there without taking your time to study.<br>Take your time to read, learn, and to figure out how to improve your own code.</p><blockquote>To get started on this, I would highly recommend getting the book "Clean Code" by Robert C. Martin (Uncle Bob).</blockquote>]]></content:encoded></item><item><title><![CDATA[To Lombok, or not to Lombok]]></title><description><![CDATA[<p>If you are reading this, chances are that you already know a <em>little</em> about <a href="https://projectlombok.org/">Project Lombok</a>. Perhaps you already love it and is looking for additional information to support your righteous claim that Lombok should be used and embraced by everyone in your team.<br>Perhaps you are the other side</p>]]></description><link>https://ivanskodje.com/to-lombok-or-not-to-lombok/</link><guid isPermaLink="false">60ace4ce41f946000126f7a0</guid><category><![CDATA[Java]]></category><dc:creator><![CDATA[Ivan Skodje]]></dc:creator><pubDate>Fri, 04 Oct 2019 11:41:00 GMT</pubDate><media:content url="https://ivanskodje.com/content/images/2021/05/photo-1569844514393-4e409050d5d7.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://ivanskodje.com/content/images/2021/05/photo-1569844514393-4e409050d5d7.jpg" alt="To Lombok, or not to Lombok"><p>If you are reading this, chances are that you already know a <em>little</em> about <a href="https://projectlombok.org/">Project Lombok</a>. Perhaps you already love it and is looking for additional information to support your righteous claim that Lombok should be used and embraced by everyone in your team.<br>Perhaps you are the other side of the coin, believing that Lombok is a potential liability that could compromise the project by having an additional dependency, and that it <em>hides</em> certain code behind annotation. Perhaps you simply do not wish everyone to spend time learning <em>something new</em>, or do not wish to force it upon someone else.</p><h2 id="what-is-lombok">What is Lombok</h2><p>The official description of Project Lombok is as followed:</p><blockquote>Project Lombok is a Java library that automatically plugs into your editor and build tools, spicing up your Java.<br>Never write another getter or equals method again, with one annotation your class has a fully featured builder, Automate your logging variables, and much more.</blockquote><p>In short, this is a compile time dependency allow you to replace common boilerplate code with certain useful annotations.</p><h2 id="when-to-use-lombok">When to use Lombok</h2><p>While Lombok provides you with plentiful of neat annotated features, I will only highlight the most commonly used ones throughout this example. I will do my best to update this list, if Lombok changes in the future - so don't hesitate to PM or comment so that we may see it.</p><blockquote>For this to work, you need to have a <code>Lombok Plugin</code> installed in your IDE.<br>For IntelliJ you can search for <code>Lombok Plugin</code> to find and install it. This will allow your IDE to understand Lombok and compile the code.</blockquote><p>Let us walk through an example. We all know these boilerplate classes that contain only getters, setters, toString, equals, and hashCode:</p><pre><code class="language-java">import java.util.Objects;

public class Project {

  private Long id;
  private String name;
  private String description;
  private String status;
  private String state;
  private int priority;

  public Long getId() {
    return id;
  }

  public void setId(Long id) {
    this.id = id;
  }

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  public String getDescription() {
    return description;
  }

  public void setDescription(String description) {
    this.description = description;
  }

  public String getStatus() {
    return status;
  }

  public void setStatus(String status) {
    this.status = status;
  }

  public String getState() {
    return ":" + state;
  }

  public void setState(String state) {
    this.state = state;
  }

  public int getPriority() {
    return priority;
  }

  public void setPriority(int priority) {
    this.priority = priority;
  }

  @Override
  public boolean equals(Object o) {
    if (this == o) {
      return true;
    }
    if (o == null || getClass() != o.getClass()) {
      return false;
    }
    Project project = (Project) o;
    return priority == project.priority &amp;&amp;
        Objects.equals(id, project.id) &amp;&amp;
        Objects.equals(name, project.name) &amp;&amp;
        Objects.equals(description, project.description) &amp;&amp;
        Objects.equals(status, project.status) &amp;&amp;
        Objects.equals(state, project.state);
  }

  @Override
  public int hashCode() {
    return Objects.hash(id, name, description, status, state, priority);
  }

  @Override
  public String toString() {
    return "Project{" +
        "id=" + id +
        ", name='" + name + '\'' +
        ", description='" + description + '\'' +
        ", status='" + status + '\'' +
        ", state='" + state + '\'' +
        ", priority=" + priority +
        '}';
  }
}
</code></pre><p>Did you see it? In <strong>one</strong> of the getters we have custom logic that is easily overlooked.</p><p>After we replace all the getter and setter methods with Lombok annotations <code>@Getter</code> and <code>@Setter</code>, it looks like this:</p><pre><code class="language-java">import java.util.Objects;
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class Project {
  private Long id;
  private String name;
  private String description;
  private String status;
  private String state;
  private int priority;
  

  public String getState() {
    return ":" + state;
  }

  @Override
  public boolean equals(Object o) {
    if (this == o) {
      return true;
    }
    if (o == null || getClass() != o.getClass()) {
      return false;
    }
    Project project = (Project) o;
    return priority == project.priority &amp;&amp;
        Objects.equals(id, project.id) &amp;&amp;
        Objects.equals(name, project.name) &amp;&amp;
        Objects.equals(description, project.description) &amp;&amp;
        Objects.equals(status, project.status) &amp;&amp;
        Objects.equals(state, project.state);
  }

  @Override
  public int hashCode() {
    return Objects.hash(id, name, description, status, state, priority);
  }

  @Override
  public String toString() {
    return "Project{" +
        "id=" + id +
        ", name='" + name + '\'' +
        ", description='" + description + '\'' +
        ", status='" + status + '\'' +
        ", state='" + state + '\'' +
        ", priority=" + priority +
        '}';
  }
}
</code></pre><p>That's a little better, right? You can clearly see that we have one getter that <em>does</em> a little more than you would normally expect it to do.</p><blockquote>Keep in mind that the compiled code will contain all the getters and setters as you would expect, and that the custom getState method will remain untouched.</blockquote><p>There is still room for improvement. We still have those generic equals and hashCode methods. Let us annotate them away, after making sure there is nothing extra going on in there.</p><pre><code class="language-java">import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
@EqualsAndHashCode
public class Project {
  private Long id;
  private String name;
  private String description;
  private String status;
  private String state;
  private int priority;


  public String getState() {
    return ":" + state;
  }

  @Override
  public String toString() {
    return "Project{" +
        "id=" + id +
        ", name='" + name + '\'' +
        ", description='" + description + '\'' +
        ", status='" + status + '\'' +
        ", state='" + state + '\'' +
        ", priority=" + priority +
        '}';
  }
}
</code></pre><p>That's better! You can now clearly see that we are using <code>@Getter</code>, <code>@Setter</code>, and an <code>@EqualsAndHashCode</code> annotation. What these annotation does should be obvious by the naming used.</p><blockquote>If you wish to exclude certain variables, you can set them explicitly in the code, or add the @Getter and @Setter annotation on the individual variables you want to use.</blockquote><p>We still have the toString method. This does not have any special formatting for printing out the values; so we can replace this with the Lombok annotation <code>@ToString</code>.</p><pre><code class="language-java">@Getter
@Setter
@ToString
@EqualsAndHashCode
public class Project {
  private Long id;
  private String name;
  private String description;
  private String status;
  private String state;
  private int priority;


  public String getState() {
    return ":" + state;
  }
}
</code></pre><p>I cannot speak for you, but to me this class is extremely easy to read and understand. We have getters, setters, toString, and the equals and hashCode in this class, and we have a single getter that does <em>something</em> extra.</p><p><strong>But wait</strong>, there is more. We are using a lot of annotations for this class. Well, perhaps not <em>a lot</em>, but more than we need. Let us replace all these annotations with the <code>@Data</code> annotation!</p><pre><code class="language-java">import lombok.Data;

@Data
public class Project {

  private Long id;
  private String name;
  private String description;
  private String status;
  private String state;
  private int priority;


  public String getState() {
    return ":" + state;
  }
}
</code></pre><p>The <code>@Data</code> annotation does several things. It provides the functionality of <code>@Getter</code>, <code>@Setter</code>, <code>@ToString</code>, <code>@EqualsAndHashCode</code> <em>and</em> <code>@RequiredArgsConstructor</code>.</p><blockquote>We will look at the different constructor annotations later below.</blockquote><h2 id="make-logging-easier">Make logging easier</h2><p>Earlier I mentioned that Lombok has a neat annotation for Logging.<br>What if we wanted to log out the state every time it was called?<br>It would look like this:</p><pre><code class="language-java">import lombok.Data;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Data
public class Project {

  private Long id;
  private String name;
  private String description;
  private String status;
  private String state;
  private int priority;

  private Logger log = LoggerFactory.getLogger(Project.class);

  public String getState() {
    String state = ":" + this.state;
    log.debug(state);
    return state;
  }
}
</code></pre><p>By using a single <code>@Slf4j</code> (or <code>@Log4j</code>) annotation it would look like this:</p><pre><code class="language-java">import lombok.Data;
import lombok.extern.slf4j.Slf4j;

@Data
@Slf4j
public class Project {

  private Long id;
  private String name;
  private String description;
  private String status;
  private String state;
  private int priority;


  public String getState() {
    String state = ":" + this.state;
    log.debug(state);
    return state;
  }
}
</code></pre><p>If you use logging in your projects, you have more than likely at some point forgot to update the class name in your Logger.</p><p>Most, if not all of these annotations completely takes out the potential human errors of writing boilerplate code. Allowing you and your co-workers to focus on what is important.</p><hr><h2 id="other-useful-annotations">Other useful annotations</h2><p>There are several other annotations that are absolutely worth mentioning.</p><h3 id="-noargsconstructor">@NoArgsConstructor</h3><p>This provides an empty constructor. If you wanted to make a private empty constructor, you can do that as well <code>@NoArgsConstructor(access = AccessLevel.PRIVATE)</code>.</p><h3 id="-requiredargsconstructor">@RequiredArgsConstructor</h3><p>This will provide you with a constructor with all the final and non-null fields.<br>and @AllArgsConstructor</p><hr><h2 id="reasons-for-not-using-lombok">Reasons for not using Lombok</h2><p>As with any library, dependency, or pretty much anything in life; there will be those that are opposed to things. If you want reasons for not using Lombok, it would most likely be one or more of the following:</p><h3 id="-you-have-to-learn-something-new">- You have to learn something new</h3><p>If you are a developer, and do not wish to continue learning, you are more than likely in the wrong profession. As a developer, you are expected to keep learning while staying relevant with technologies as it advances.</p><h3 id="-if-we-someday-decide-to-stop-using-lombok-it-would-take-a-long-time-to-undo-it">- If we someday decide to stop using Lombok, it would take a long time to undo it</h3><p>Lombok has a <a href="https://projectlombok.org/features/delombok">Delombok</a> tool that can be run on your project. This will replace all annotations with the actual code.</p><h3 id="-everyone-that-is-going-to-work-on-the-project-have-to-install-a-lombok-plugin-to-work-on-the-project">- Everyone that is going to work on the project have to install a Lombok Plugin to work on the project</h3><p>This is probably the most valid point against the use of Lombok.<br>If you have users in your team that use a custom IDE (or using notepad, VIM, or something else silly-willy) it may be difficult to work with projects that use Lombok.<br>That being said, both IntelliJ, Eclipse and Netbeans support the use of Lombok, and even if you don't intend to use it yourself, you should install it so your team may use it at its fullest.</p><blockquote>If you are using an IDE that do not support Lombok, please comment below.</blockquote><h2 id="introducing-lombok-to-your-team">Introducing Lombok to your Team</h2><p>Okay, so you are convinced that Lombok is the correct choice. How would you go about convincing your team to try it out?</p><h3 id="provide-sample-code">Provide sample code</h3><p>Find a class that has a lot of getters and setters.<br>Find a class that contains a constructor that takes in all fields.<br>Lombokify it! Show the team how incredibly easy it is to read the class without all the horrific and useless getters and setters.<br><strong>We don't need to read all the getters and setters unless it does something extra!</strong></p><h3 id="demonstrate-the-most-useful-annotations">Demonstrate the most useful annotations</h3><ul><li><code>@Getter</code></li><li><code>@Setter</code></li><li><code>@ToString</code></li><li><code>@EqualsAndHashCode</code></li><li><code>@Data</code></li><li><code>@NoArgsConstructor</code></li><li><code>@RequiredArgsConstructor</code></li><li><code>@AllArgsConstructor</code></li><li><code>@SLf4j</code></li><li><code>@Log4j / @Log4j2</code></li></ul><p>Ask them if they can guess what each if these fields do. Luckily, most of these are self-explanatory.</p><p>The annotations you should spend some extra time explaining are:<br><code>@Data</code> is just a collection of the previously mentioned annotations.<br><code>@RequiredArgsConstructor</code> only includes the final and non-null fields.<br><code>@AllArgsConstructor</code> includes all non-null fields.<br><code>@Slf4j</code> provides an invisible <code>log</code> variable that can be used and accessed to write logs without having to setup a Logger manually in each class.</p><p><strong>Super easy!</strong></p><hr><h2 id="a-conclusion">A conclusion?</h2><p>What we really are learning from this, is that Java still is a flawed language. It is quickly becoming "ye olde" of programming languages, and it might be left behind unless it can keep up with languages such as Kotlin.<br>After all, we want to code. We don't want to read all the generic unmodified getters and setters.<br>We want the code to be easy to read and understand.</p><p>Meanwhile, I recommend that you try out <a href="https://projectlombok.org/">Project Lombok</a> in your projects.<br>I promise you will love and embrace it once you get used to reading it.</p><hr><p>Find more info and annotations at <a href="https://projectlombok.org/">https://projectlombok.org/</a></p>]]></content:encoded></item><item><title><![CDATA[String comment; //this is a comment]]></title><description><![CDATA[<pre><code class="language-java">// This is the name of the header
private String headerName;

/**
 * Returns the name of the header
 * @return Header name as a String
 */
public String getHeaderName() {
  return headerName;
}
</code></pre><p>Have you ever seen comments like these?<br>Are you not grateful for it?<br>Don't you just <strong>wish</strong> you could find that individual and</p>]]></description><link>https://ivanskodje.com/string-comment-this-is-a-comment/</link><guid isPermaLink="false">60ace3f341f946000126f795</guid><category><![CDATA[Clean Coding]]></category><dc:creator><![CDATA[Ivan Skodje]]></dc:creator><pubDate>Tue, 10 Sep 2019 08:31:00 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1572231754710-4bed19649091?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDF8fGNvbW1lbnRzfGVufDB8fHx8MTYyMTk0MzQ1Ng&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<pre><code class="language-java">// This is the name of the header
private String headerName;

/**
 * Returns the name of the header
 * @return Header name as a String
 */
public String getHeaderName() {
  return headerName;
}
</code></pre><img src="https://images.unsplash.com/photo-1572231754710-4bed19649091?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMTc3M3wwfDF8c2VhcmNofDF8fGNvbW1lbnRzfGVufDB8fHx8MTYyMTk0MzQ1Ng&ixlib=rb-1.2.1&q=80&w=2000" alt="String comment; //this is a comment"><p>Have you ever seen comments like these?<br>Are you not grateful for it?<br>Don't you just <strong>wish</strong> you could find that individual and ... shake his or her hand?</p><blockquote>Thank you for that insightful comment!<br>Without this comment I would never have figured out <em>what</em> we would store in this mysterious variable!<br>Three blessing upon thee!</blockquote><p>Sarcasm and exaggerated example aside; at some point in most developers young life, you might have thought that writing long and descriptive comments for every method is a good thing.<br>After all, <em>how</em> would anyone besides yourself be able to understand what the code does? We would not want the other developers waste his or her time, trying to read the code.<br>Unfortunately, I did this too. Until learning what readable code really is all about.</p><p>Please allow me to elaborate further why it is my firm belief that the policy of writing comments is <em>generally</em> a bad thing, and why it should be avoided whenever possible.</p><hr><h2 id="student-goodstudent-a-good-student-writes-comments">Student goodStudent; // A good student writes comments</h2><p>In school they teach you to comment your code. Comment classes. Comment methods. Comment variables. Comment everything.<br>Why do you believe that is so? Why do you learn at most schools to comment your code during your education? Some might say it is for the teachers benefit, in order for them to understand your code without having to read the code. Perhaps the teacher expects you to elaborate your programming choices.<br>A better reason to do so, would be to force you to <em>spell out</em> what you think the code does, so that the teacher may read it and see whether or not you understand the code.<br>Whatever the reason is, the result may be unfortunate because we never learn when not to comment. You are practically brainwashed to <em>comment your code</em> from the beginning of your developer journey.</p><p>I recall working with other students who told me that their teachers graded them based on their comments. Imagine dropping in grades because you <em>forgot</em> to write comments! How would you as a student know better, of course you would trust your teacher to teach you <em>best coding practices</em>.</p><p>Why is it that we are not learning how to write readable code? We learn how to read and write a programming language, sure. We learn the syntax, differences between modifiers, the range of scope, object oriented programming (OOP), and so on.<br>We do not, however, seem to be learning <em>how to write readable code</em> in such a way that comments are made redundant. At the very least, I would expect to be <em>informed</em> that there even is such a thing as <em>readable code</em>, and that comments should not be needed.</p><p><strong>Not once</strong> did we sit down and talk about how the real professionals go about writing <em>understandable and readable code</em>, that does not have comments that simply state out what the code does.<br>Instead what we have is mediocre code with a bunch of comments explaining what it does.</p><p>Comments are the failure of writing readable code!</p><hr><h2 id="cards-tablecards-cards-on-the-table">Cards tableCards; // Cards on the Table</h2><p><strong>Are comments useful?</strong><br>Yes. Well, that depends. They certainly can be. There will always be times when you write code that you simply <em>cannot</em> refactor or rename in such a way that the reader would understand <em>crucial</em> information.<br>Does that mean we <em>should</em> do it? Most certainly not. It should be avoided like the plague. Comments rot, spread, and slowly infest the code base with noise and misleading information caused by developers either forgetting or neglecting to update the comments. Sometimes the comment is simply plain wrong, or badly written.</p><p><strong>When should I use comments?</strong><br>When you are unable to convey what is going on in the code itself.<br>When understanding the method required to you know essential context information.</p><p>Frankly I would rather spend 5 minutes trying to figure out a single good and descriptive method name, rather than taking the <em>easy</em> path of writing comment that explains what a method is doing; which more than likely simply lists out what a method does.</p><p><strong>Should I comment a method when it can return more than one thing?</strong><br>You might have read comments that state something similar to; "this method may return null", or "this method will return one item on X circumstance, but more in Y circumstance".</p><p>If your method does more than <em>one thing</em>, it probably should be split up into several smaller methods that each do <em>one thing</em>. Perhaps even into a class.</p><blockquote>No developer should expect a method to return <code>null</code>. We can use <code>Optional</code> instead.</blockquote><p><strong>Long methods should have comments!</strong><br>Yes, a long method would be difficult to read and understand without an informative comment.</p><p>However, what you might as well say is this;</p><ul><li>The method is too long</li><li>The method is unreadable</li><li>I would rather just comment this instead of cleaning it up, or</li><li>I don't know how to clean this up</li></ul><p>The latter is normal, everyone have at some point written code that is too complex and difficult to read and understand. Does that mean we should give up and write comments? Probably not.</p><p>What you as a developer <em>should</em> be doing, is to work towards improving yourself. Self-study. Practice code katas. Read books. Watch youtube videos about readable code, clean code, and practice test driven development (TDD).</p><p>You should make it a personal challenge to make that piece of code readable instead of adding a comment to it. Yes, it may cost 5 minutes. 30 minutes. Perhaps even an hour.</p><blockquote>TDD is a good way to practice writing readable code. Your code would have to be written in such a way that it becomes testable, which usually has the side effect of being more readable with good naming and separation of concerns.</blockquote><p><strong>What about legacy systems?</strong><br>Many of us have been there. Working with legacy code can suck. There is no denying it.<br>However there are <strong>always</strong> options for how you may keep adding/modifying legacy code without adding to the chaos. In fact, you may slowly be improving the code over time - making it more readable. Without adding more to the comments.</p><p>If you are working on a long method that would be very difficult to refactor and rename into smaller readable methods, I would probably want to see that at least the <em>new code</em> and <em>new modifications</em> are readable without needing to add comments.<br>I simply do not want, or need, to have duplicate information that simply echos what is happening in my code base.</p><p><strong>Are there too many comments in my code?</strong><br>If you are reading through code with a <em>lot of</em> comments between every variable and method, at some point you will stop reading them.<br>If not you, then another developer. Many developers have learned that comments lie. Comments trick you. Comments waste your time, because most of the time after reading a comment, you begin reading the code.<br>In the end, comments are just fluff invented decades ago to help coders understand very difficult code. In a time when comments <strong>were</strong> useful. Not only useful, but a necessity!</p><p>The consequence?</p><blockquote>You will miss and ignore the important comments that <strong>are</strong> helpful!</blockquote><hr><h2 id="localdatetime-storytime-several-years-ago">LocalDateTime storyTime; // Several years ago</h2><p>When I first begun working in my first <strong>real</strong> developer job, I did as any fresh student would do; I followed lessons that I had been thought at school.</p><p>I followed the new-dev routines and familiarized myself with the developer guidelines. I was introduced to new tech, Spring Boot - and begun learning and reading code.</p><p>When I started coding, doing the "real work", I made sure to comment all my code, following javadocs standard perfectly.<br>After a while I noticed that no one else was doing that, and figured that perhaps they were lazy, or didn't care enough whether or not people did write them.<br>Fair enough, I kept doing it because that is what I had been thought.<br>My comments and code was beautiful. Between each line you could see comments that in themselves formed a sort of symmetry. A flow. Predictability.</p><p>A few months passes, and I've gotten a few comments about my in-code-comments.</p><blockquote>"Why did you comment this? I don't think you need to comment this, it it pretty obvious this returns a String!"</blockquote><p>I agreed, but argued that by following javadocs formatting, we would get a very nicely readable documentation we can look at. We can even print it out and it would be wonderfully easy to see what everything is and does.</p><p>Sure, I was not <em>wrong</em>, but I was not right either.<br>I later came to realize is that no one (including myself) don't really <em>care</em> about javadocs, because there have never even been the need to look at it.<br>I just loved the <em>idea</em> of having it, because it was beautiful.</p><h3 id="beauty-will-blind-you">Beauty will blind you</h3><p>After some time, I got more comments (the verbal kind). No one actually <em>told</em> me to stop writing comments, but I started to realize that no one really <em>wanted</em> comments. I would save myself a lot of trouble and time by simply <em>not</em> writing them.</p><blockquote>"If they don't want comments, then I am not going to give them any!" - Old me</blockquote><p>I stopped writing comments. I didn't remove the old comments I did, but I made sure not to write new ones!<br>Some time passes. I become more accustomed to having to read code without comments. I no longer have comments that are holding back information, nor do I have comments that are misleading, or comments that is lying to me.<br>No longer was there a man at the front door, telling me what is behind the door. I just enter, take a look, and then I'm out again.</p><p>During a code review, I discovered a method that I could not for the life of me figure out what it does. It had a method name <code>perform</code> and did many small things. The name of the class has the format <code>xHandler</code>. Sure, it handles x, but what does it <em>do</em>? It performs something! A dance perhaps?</p><blockquote>"What does this method do? It's too long and complex to understand as-is, it needs a comment!" - Old me</blockquote><p>The other developer agrees that is is difficult to understand, excusing himself explaining it took a long time to write and that he neglected to clean it up because he was tired working on it. Something I can fully understand.<br>The developer then performs the <em>fix</em> and sends it back to me.<br>I notice that the class name and method name had changed, but I did not see the comment I expected! <code>#Reeeeee!</code><br>After I had finished fuming over the lack of a comment, I noticed other changes in the code. The method had become smaller somehow, and a few other methods had been added. There was even a new class with 2-3 methods.<br>I read the code again, and <em>lo and behold</em>, I could understand what the code did now. It only did <em>one</em> thing, it started <code>x</code>. It used the new class to perform the initial setup preparations, and then the class itself did the startup. So simple. I did not even have to <em>read</em> the innermost code to understand what is going to happen. It is written plain as day. Human readable text.<br>To be more specific: <code>humanReadableText()</code></p><p>I told him the code looks very good, and I was no longer disappointed that he did not follow my advice to write comments. He instead made it better. He made it <strong>readable</strong>. No need for comments to begin with!</p><h4 id="why-did-i-initially-want-comments-added">Why did I initially want comments added?</h4><p><strong>Because the code was difficult to read and understand!</strong><br>Comments should <strong>not</strong> be a go-to solution to cover bad code. It is a absolute last resort.<br>Comments are spawned from inadequate code. Code that has room for improvements. Code that is begging to be refactored and renamed. Code that wants to read!</p><p><strong>Writing readable code is the hardest there is!</strong><br>There is no doubt about it. I can spend several minutes trying to pick my brain figuring out what to name this... <code>thing</code>. It is nothing to be ashamed of.<br>Once you become better at it, doing it more and more, you will notice when you get back into your old code that you can still follow it. You do not have to <em>remember</em> what you write, you can simply navigate it because it is written <em>readable</em>.</p><h4 id="commented-distractions">Commented distractions</h4><p>Comments caused distractions away from what matters: the code.</p><h5 id="distractions-">Distractions...</h5><ul><li>caused by comments that tell half-truths of what a method does.</li><li>caused by having outdated comments.</li><li>caused by comments saying one thing, while the code saying another.</li><li>caused by lying comments written by a developer that thought the method did X, when it really did Y.</li></ul><hr><h3 id="continuing-the-story">Continuing the story</h3><p>At some point, we got a new consultant in our team. A swedish fellow. Very good at what he does.</p><p>After some time we slowly got introduced to a different way of writing code. Test Driven Development. Testable code. With methods that are short and concise.<br>A side effect of this was having more than the usual one or two <em>long</em> methods. Instead we have plenty of smaller methods that does exactly what the method name said it does.<br>If I were to jump into a random method, it might be a bit difficult to understand what it is used for, and where it goes.</p><p><strong>However</strong>, from the top level it was very easy to follow along, understanding and reading the code.</p><p>Common arguments against multiple smaller methods are:</p><ul><li>The code is difficult to follow</li><li>It's only 1-2 lines</li><li>Reading this method does not help me understand where it is used</li></ul><p>To that I say: When you start reading a new book, <strong>you don't start in the middle of the book</strong> expecting to be able to understand what have happened beforehand!<br>You start at the <strong>beginning</strong>!</p><h2 id="summary-summary-a-summary-of-things">Summary summary; // A summary of things</h2><p>There is a lot of bits and pieces to take from this. If I had to pick out the most important lessons I have personally gained most from, it would probably be this.</p><ul><li><strong>Comments should be avoided whenever possible.</strong></li></ul><blockquote>That does not mean it should <em>never</em> be written. You should definitely write comments in the <strong>1%</strong> scenarios when it would provide essential intel for the next developer. However the <strong>1%</strong> cases is absolutely not an excuse for developers to comment most methods, as many does.</blockquote><ul><li><strong>Practice writing readable code.</strong></li></ul><blockquote>The greatest resource you have available to you, are your fellow developers. Be it young or old.<br>Don't hesitate to ask for eyes on the code you are writing. Can any of you think of a way to make it readable? If not, hang your heads in shame and write the comment; begging for forgiveness from all your readers.</blockquote><ul><li><strong>When you have to write comments, make sure it explains "why", and not the "what"</strong></li></ul><blockquote>Don't simply write out what a method does. In the very very few cases a comment would be appropriate, 99% of the time is should be the <strong>why</strong>.</blockquote><ul><li><strong>Avoid writing monster methods.</strong></li></ul><blockquote>Your code should tell the story. If a developer have to read complex math problems or algorithms to understand what a method does, you have code that probably should be split into readable methods or variables.</blockquote><blockquote>By the way, if you have a method with 3-4 input variables that are used throughout several methods; chances are that you got a hidden <strong>class</strong> that is waiting to be created.</blockquote><hr><h3 id="having-trouble-refactoring-legacy-code">Having trouble refactoring legacy code?</h3><p>I would highly recommend the book <strong>Working Effectively with Legacy Code</strong>, by Michael Feathers.<br>It will provide you with easy-to-do strategies to help you work with "old code", while making sure that the new code you write is readable and understandable.<br>The <em>sprout methods</em> and <em>classes</em> are easily my most common strategies when working with older legacy code, and there are circumstances other methods may apply.</p><hr><p>Please share your own experiences with good and bad comments in the <em>"comments"</em> below!</p>]]></content:encoded></item><item><title><![CDATA[Bad code, Readable code]]></title><description><![CDATA[<p>Bad code. Words that ring in our developer ears every time we hear it.<br>We all know about it. You have written it. I have written it. We have all written it.</p><p>What is it though? How do you know whether or not the code you write is "good" or</p>]]></description><link>https://ivanskodje.com/bad-code-readable-code/</link><guid isPermaLink="false">60ace33e41f946000126f784</guid><category><![CDATA[Clean Coding]]></category><dc:creator><![CDATA[Ivan Skodje]]></dc:creator><pubDate>Fri, 09 Aug 2019 07:04:00 GMT</pubDate><media:content url="https://ivanskodje.com/content/images/2021/05/jibrish.png" medium="image"/><content:encoded><![CDATA[<img src="https://ivanskodje.com/content/images/2021/05/jibrish.png" alt="Bad code, Readable code"><p>Bad code. Words that ring in our developer ears every time we hear it.<br>We all know about it. You have written it. I have written it. We have all written it.</p><p>What is it though? How do you know whether or not the code you write is "good" or "bad"?  Is it simply code that your scrum master would consider engineering excellence? Perhaps it is just bug free code, or code written in the fastest amount of time. Surely the company that employ you would value spending the least amount of time on a "task" the most, so that would obviously be the "best code", right?</p><p>No, I would go so far to claim- nay. I would go so far to <em>state</em> that <strong>bad code is code that is difficult to read and understand</strong>.</p><blockquote>"You must be crazy! Surely code that is stable/bug free is far more important!"</blockquote><p>Aha! You would be wrong! -that is, unless the stable/bug free code is code that never ever see the light of day again by another human being.<br>Then I would give you a gold star and commend you for your work</p><figure class="kg-card kg-image-card"><img src="https://blogg.itverket.no/content/images/2019/07/gold_star.png" class="kg-image" alt="Bad code, Readable code"></figure><p>Realistically speaking we will more than likely be coming back to the code at some point in time. Perhaps not by you, but by another developer. Having then to read and understand the code all over again.<br>Every time you are spending more than a few seconds, minutes or, Thanos forbid; <strong>hours</strong> trying to read and understand a method, you are witnessing bad code in action. It is simply not written with readability in mind.</p><hr><h2 id="less-talk-more-walk">Less talk, more walk</h2><p>Let us look at a very small and <em>heavily simplified</em> code example, and I will do my best to narrate the thought processes we all have had at some point.</p><p>Keep in mind that this is just one example that barely scratches the surface of the substance found in "bad code". There is never just one way of writing bad code, as there is never just one way of writing good code.</p><blockquote>I would encourage you to note down the good and the bad in this code snippet before continuing, so that we may share notes</blockquote><pre><code class="language-java">class Main {
  public static void main(String[] args) {
    NumberUtil numberUtil = new NumberUtil();
    double r = 10;
    double h = 2;
    double v = numberUtil.getVolume(r, h);
    double sa = numberUtil.getSurfaceArea(r, h);
    System.out.println(v);
    System.out.println(sa);
  }
}
class NumberUtil {
  public double getVolume(double r, double h) {
    return Math.PI * r * r * h;
  }
  public double getSurfaceArea(double r, double h) {
    return 2.0 * Math.PI * r * (r + h);
  }
  public double getSquared(double n) {
    return n * n;
  }
}
</code></pre><p>Is this the bad code, or is this the good code?</p><p>We have a <strong>Main class</strong> that contains the famous public static void main (psvm) method.<br>It uses a NumberUtil in order to call two methods; <em>getVolume</em> and <em>getSurfaceArea</em>.<br>NumberUtil also have a third method that does not appear to be in use; <em>getSquared</em>.</p><p>We are setting two variables; <em>r</em> and <em>h</em>, which are used as parameters in the two methods used. Knowing the method names, we can with a high degree of certainty be sure that <em>r</em> is radius. Perhaps it <em>could</em> be radian, if it is an incomplete shape, but it is <strong>most</strong> likely the radius. <em>h</em> must be height then. Cannot calculate volume without an height, right?</p><p>Looking in the <em>getVolume</em> method, we see that we are using PI * r * r * h, so we are probably working with some sort of circular shape doodad. Same variables are used in <em>getSurfaceArea</em> too.</p><p>We are also setting <em>sa</em> and <em>v</em>. Knowing the method names used to set them, we can be sure that <em>sa</em> means Surface Area, and <em>v</em> means Volume.</p><p>Then we print out those variables at the end. We use the r and h, to get sa and v in order to print them out. Wonderfully easy.</p><h3 id="what-is-wrong">What is wrong?</h3><p>Well, technically nothing was <strong>wrong</strong> with this code. It works. There is no way to produce bugs in this code without changing or moving things around.<br>It is the perfect code.</p><p><strong>I lied</strong>. There are many elements of "wrong" in this code. The "wrong" is that this code is "bad code".</p><blockquote>Bad code is wrong. Don't do it.</blockquote><p>Let us look at the variables again. We have r, h, v, sa.<br><em>There!</em> You just did it again!<br>In order to identify what r, h, v, and sa is, you had to form a mental <code>Map&lt;String, String&gt;</code> in order to identify what they are. A mental map.<br>r = radius,<br>h = height,<br>v = volume,<br>sa = sand area. Or was it surface area? Woops.</p><p>In your mind you now have an <strong>unnecessary</strong> <code>Map&lt;String, String&gt;</code> in memory. It is taking attention away from what is <em>really</em> important. We <strong>dont want</strong> to keep mental map of the variables being read throughout the code. It is extra work.<br>No one wants to scroll through the code, only to find abbrevations, then scrolling up, finding out what it is, and then scroll down again.</p><blockquote>"But using abbrevations shortens the time I use to type!"</blockquote><p><strong>Nu-uh.</strong> Anyone that is not you will guaranteed spend more time figuring out what each and every abbrevation stands for; at least once each. Even <em>you</em>, when taking longer breaks from the code, will have to rebuild your mental <code>Map&lt;String, String&gt;</code> when coming back to the code.</p><p>Imagine if this example was longer, more complex, where the variables might have been used in several places! An unnecessary use of your finger strength. Save it for the gym instead.</p><p>No, the obvious solution to this is to refactor and rename the variable names so they become readable.</p><pre><code class="language-java">class Main {
  public static void main(String[] args) {
    NumberUtil numberUtil = new NumberUtil();
    double radius = 10;
    double height = 2;
    double volume = numberUtil.getVolume(radius, height);
    double surfaceArea = numberUtil.getSurfaceArea(radius, height);
    System.out.println(volume);
    System.out.println(surfaceArea);
  }
}
class NumberUtil {
  public double getVolume(double radius, double height) {
    return Math.PI * radius * radius * height;
  }
  public double getSurfaceArea(double radius, double height) {
    return 2.0 * Math.PI * radius * (radius + height);
  }
  public double getSquared(double n) {
    return n * n;
  }
}
</code></pre><p>Ahh, our minds can now rest and be assured that volume is volume, surfaceArea is sandArea- I mean surfaceArea; and that the remaining variables are what they are supposed to be. No need to drag the entire world of context into the picture.</p><p>If I were to ask you; what exactly, is the shape of the "volume" we are calculating?<br>Let us rename it.<br>Let us see, NumberUtil has three methods, getVolume, getSurfaceArea and getSquared... hold on. What does getSquared has to do with the other methods?<br>(Spoiler: Nothing)</p><p>What we have here, is a class with an unuseful and generic name. Let us fix that- and while we are at it, let us clean the methods a bit.</p><pre><code class="language-java">class Main {
  public static void main(String[] args) {
    double radius = 10;
    double height = 2;
    Cylinder cylinder = new Cylinder(radius, height);
    double volume = cylinder.getVolume();
    double surfaceArea = cylinder.getSurfaceArea();
    System.out.println(volume);
    System.out.println(surfaceArea);
  }
}

class Cylinder {

  private double radius;
  private double height;

  public Cylinder(double radius, double height) {
    this.radius = radius;
    this.height = height;
  }

  public double getVolume() {
    return Math.PI * radius * radius * height;
  }

  public double getSurfaceArea() {
    return 2.0 * Math.PI * radius * (radius + height);
  }
}
</code></pre><p>We were working with a cylinder all along!<br>Now there is no doubt what this is. No prior math experience needed. No need to read into the function in order to figure out the equation. We have Cylinder that takes in a radius and height in order to be constructed, and it will return the information you need when asked.<br>Sweet.</p><p>What else could possibly be wrong here? Its readable, right?<br>Well, it could be better. We have several lines of code that does something when put together. We have created a Cylinder so that we may fetch the Volume and Surface Area from it, for the purpose of printing it out.</p><p>"Tell, do not ask": Fameous words that can be read throughout several Uncle Bob books and videos.</p><p>Why go through the trouble of doing the work ourselves, when we can simply tell something what to do!</p><pre><code class="language-java">class Main {
  public static void main(String[] args) {
    double radius = 10;
    double height = 2;
    Cylinder cylinder = new Cylinder(radius, height);
    cylinder.printVolume();
    cylinder.printSurfaceArea();
  }
}

class Cylinder {

  private double radius;
  private double height;

  public Cylinder(double radius, double height) {
    this.radius = radius;
    this.height = height;
  }

  public double getVolume() {
    return Math.PI * radius * radius * height;
  }

  public double getSurfaceArea() {
    return 2.0 * Math.PI * radius * (radius + height);
  }
  
  public void printVolume() {
    System.out.println(getVolume());
  }
  
  public void printSurfaceArea() {
    System.out.println(getSurfaceArea());
  }
}
</code></pre><p>Now it is clear as reading text on paper what is going on.<br>We have a cylinder with radius 10, and height 2.<br>We are then printing out the volume and surface area.<br>All without needing to look at how we do it.<br><strong>Tell, do not ask.</strong></p><h2 id="comparison">Comparison</h2><p>Let us compare the "before" and the "after".</p><pre><code class="language-java">class Main {
  public static void main(String[] args) {
    NumberUtil numberUtil = new NumberUtil();
    double r = 10;
    double h = 2;
    double v = numberUtil.getVolume(r, h);
    double sa = numberUtil.getSurfaceArea(r, h);
    System.out.println(v);
    System.out.println(sa);
  }
}
class NumberUtil {
  public double getVolume(double r, double h) {
    return Math.PI * r * r * h;
  }
  public double getSurfaceArea(double r, double h) {
    return 2.0 * Math.PI * r * (r + h);
  }
  public double getSquared(double n) {
    return n * n;
  }
}
</code></pre><pre><code class="language-java">class Main {
  public static void main(String[] args) {
    double radius = 10;
    double height = 2;
    Cylinder cylinder = new Cylinder(radius, height);
    cylinder.printVolume();
    cylinder.printSurfaceArea();
  }
}

class Cylinder {

  private double radius;
  private double height;

  public Cylinder(double radius, double height) {
    this.radius = radius;
    this.height = height;
  }

  public double getVolume() {
    return Math.PI * radius * radius * height;
  }

  public double getSurfaceArea() {
    return 2.0 * Math.PI * radius * (radius + height);
  }

  public void printVolume() {
    System.out.println(getVolume());
  }

  public void printSurfaceArea() {
    System.out.println(getSurfaceArea());
  }
}
</code></pre><p>In a more complex software, I would most likely have opted for a wrapper class in order to provide additional functionality to the Cylinder - instead of adding print methods directly on it.</p><p>Oh, where did the <code>getSquared</code> method go? I threw it away. Because it was a SPAMM (Stupid Pointless and Meaningless Method). Guess what, if your coworker will need that single method days from now, weeks from now, or years, s/he can go fish in the git commit logs.</p><h2 id="key-points">Key Points</h2><p>The key points to take away from this is;</p><ul><li>Do <strong>not</strong> use abbreviations</li><li>Separate responsibilities</li><li>Tell, do not ask</li><li>Get rid of SPAMM, mercilessly</li><li>Avoid generic naming of classes and methods</li><li>Keep learning, and share what you learn</li></ul><hr><h2 id="recommended-resources">Recommended Resources</h2><p>All of these are highly recommended if you wish to dive deeper into the rabbit hole, to uncover ways of handling bad code, as well as to learn and practice writing better code.</p><h3 id="books">Books</h3><ul><li>Clean Code: A Handbook of Agile Software Craftsmanship, by Robert C. Martin (Uncle Bob)</li><li>Working Effectively with Legacy Code, by Michael Feathers</li><li>The Pragmatic Programmer: From Journeyman to Master, by Andrew Hunt &amp; David Thomas</li></ul>]]></content:encoded></item><item><title><![CDATA[Check if command exist using Bash]]></title><description><![CDATA[<p>A nifty code snippet that will allow you to check for the availability of a command.</p><pre><code class="language-bash">#!/bin/bash

if type "command-name" &amp;&gt; /dev/null; then
echo "Command exist!"
else
echo "Command does not exist! :("
fi</code></pre><p>Used this myself for a script, where I wanted to make sure a certain</p>]]></description><link>https://ivanskodje.com/using-bash-script-to-check-if-a-command-exist/</link><guid isPermaLink="false">60b0c5b6de267d000118fcca</guid><category><![CDATA[GNU/Linux]]></category><dc:creator><![CDATA[Ivan Skodje]]></dc:creator><pubDate>Sat, 18 Aug 2018 06:59:00 GMT</pubDate><content:encoded><![CDATA[<p>A nifty code snippet that will allow you to check for the availability of a command.</p><pre><code class="language-bash">#!/bin/bash

if type "command-name" &amp;&gt; /dev/null; then
echo "Command exist!"
else
echo "Command does not exist! :("
fi</code></pre><p>Used this myself for a script, where I wanted to make sure a certain command was available before continuing the script execution.<br>That being said; it was a bit of a pain to figure out how to do this, while searching the web for answers!<br></p><p>Solution discovered, solution shared.</p>]]></content:encoded></item><item><title><![CDATA[How to set system path environment variable in Windows 10]]></title><description><![CDATA[<p>You may have encountered the term "system environment variables" from time to time. <br>More specifically, system <strong>path</strong> environment variable. This is often talked about in context of running executable files via the command line or powershell window.</p><p>In this example, I will demonstrate how you would install and run <a href="http://cmder.net/">cmder</a></p>]]></description><link>https://ivanskodje.com/how-to-set-system-path-environment-variable-in-windows-10/</link><guid isPermaLink="false">60b0c4e9de267d000118fca6</guid><category><![CDATA[Windows]]></category><dc:creator><![CDATA[Ivan Skodje]]></dc:creator><pubDate>Mon, 16 Jul 2018 08:05:00 GMT</pubDate><media:content url="https://ivanskodje.com/content/images/2021/05/RmbpAds.png" medium="image"/><content:encoded><![CDATA[<img src="https://ivanskodje.com/content/images/2021/05/RmbpAds.png" alt="How to set system path environment variable in Windows 10"><p>You may have encountered the term "system environment variables" from time to time. <br>More specifically, system <strong>path</strong> environment variable. This is often talked about in context of running executable files via the command line or powershell window.</p><p>In this example, I will demonstrate how you would install and run <a href="http://cmder.net/">cmder</a> from the command line. <strong>You are of course free to do this for any application containing executable files.</strong></p><blockquote>Cmder is a terminal emulator for Windows, and I highly recommend you try it out if you are a developer that is comfortable using a terminal. (May no longer be relevant as PowerShell have improved considerably since the time I wrote this)</blockquote><p><strong>GOAL: </strong>Our task is done when we are able to open Cmder.exe via the command line, regardless of current directory path.<br>Without adding the system path environment variable, you would not be able to run Cmder.exe without navigating to the folder containing the file.</p><h1 id="how-to-setup-a-system-path-environment-variable">How to setup a system path environment variable</h1><ol><li>Download the application/tool you wish to setup a system path environment variable for. E.g. <a href="http://cmder.net/">cmder</a>.</li><li>Unzip and place the folder somewhere. In this example, I will place it in <strong>C:\cmder\</strong>.  <em>Make sure the folder containing the executable file(s) you wish to run is the folder path you use hereforth. It is common for applications to have a \app\bin\ folder containing the executable files. However in our example, the executable file is in the \cmder\ folder. </em></li><li>Open <strong>Edit the system environment variables</strong> page on Windows, using the Start Menu, and by starting to type it out.</li></ol><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/b8TzxGY.png" class="kg-image" alt="How to set system path environment variable in Windows 10"></figure><ol><li>Select <strong>Environment Variables...</strong></li></ol><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/RmbpAds.png" class="kg-image" alt="How to set system path environment variable in Windows 10"></figure><ol><li>Double-click the <strong>Path</strong> variable.</li></ol><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/ovmOi43.png" class="kg-image" alt="How to set system path environment variable in Windows 10"></figure><ol><li>Select <strong>New</strong> and enter the path to the folder containing the binary files. For example using cmder, it would be <strong>C:\cmder</strong></li></ol><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/2iwzy6L.png" class="kg-image" alt="How to set system path environment variable in Windows 10"></figure><ol><li>Select OK and exit.</li><li>Open up command prompt or powershell (if you already got one open, make sure to close and re-open it).</li><li>Type in Cmder.exe and hit enter. Cmder should open regardless if there is, or isn't any Cmder.exe file in the current command line directory.</li><li><strong>Bask in the glory of success.</strong></li></ol><p><em>Feel free to drop by a comment if you have feedback or need some help.</em></p>]]></content:encoded></item><item><title><![CDATA[Easy way to download music playlists from YouTube]]></title><description><![CDATA[<blockquote>This is for educational purposes only. It is your responsibility to verify whether or not YouTube allows you to do this.</blockquote><p>I've recently found a playlist I wanted to "<em>git pull</em>" from <a href="https://www.youtube.com/">YouTube</a>, but realize it would take too long to use tools such as Keepvid, or any other MP3</p>]]></description><link>https://ivanskodje.com/easy-way-to-download-music-playlists-from-youtube/</link><guid isPermaLink="false">60b0c42ede267d000118fc98</guid><category><![CDATA[Info]]></category><dc:creator><![CDATA[Ivan Skodje]]></dc:creator><pubDate>Sun, 01 Jul 2018 07:54:00 GMT</pubDate><media:content url="https://ivanskodje.com/content/images/2021/05/youtube-playlist-gist.png" medium="image"/><content:encoded><![CDATA[<blockquote>This is for educational purposes only. It is your responsibility to verify whether or not YouTube allows you to do this.</blockquote><img src="https://ivanskodje.com/content/images/2021/05/youtube-playlist-gist.png" alt="Easy way to download music playlists from YouTube"><p>I've recently found a playlist I wanted to "<em>git pull</em>" from <a href="https://www.youtube.com/">YouTube</a>, but realize it would take too long to use tools such as Keepvid, or any other MP3 fetchers online. Then I recalled that there is this neat little tool called<strong> </strong><a href="https://rg3.github.io/youtube-dl/">youtube-dl</a>, and decided to learn how to use it in order to download MP3s from YouTube.<br>To my big surprise, it also allows us to <strong>download entire playlists</strong>.</p><p>As a result, I've spent some time to write down a short <a href="https://gist.github.com/ivanskodje/5e6f4f7d883cd1124d6d0680b51c4cd9">step by step guide</a> for Windows, that should help us download <strong>entire youtube playlists</strong>, as well as <strong>individual</strong> MP3s from youtube videos.</p><p>I expect to append a similar guide for Linux in the same gist file, once I get the time.</p>]]></content:encoded></item></channel></rss>