Welcome!

Welcome to Kirsle.net! This is the personal website of Noah Petherbridge, and it's where my various software projects and web blog lives.

Why Tabs are Better than Spaces

Noah Petherbridge
kirsle
Posted by Noah Petherbridge on Thursday, June 15 2017 @ 03:34:13 PM

This is going to be yet another blog post in the "tabs vs. spaces" holy war that software developers like to fight about. I generally prefer tabs over spaces, but for certain types of programming languages I do use spaces instead of tabs.

What's all this, then?

If you've been living under a rock and haven't heard of this holy war, here's an example. Here's some source code:

func main() {
    server := NewServer()

    if os.Getenv("DEBUG") != "" {
        fmt.Println("Debug mode is on")
        server.Debug = true
    }

    server.Run()
}

Programmers who prefer to use tab characters to indent their code would write the code using tab symbols for each level of indentation (two tabs for the inside of that if statement). Programmers who prefer to indent with spaces, on the other hand, would've used 4 spaces to get the above code per indent level, with the if block indented by 8 spaces (two indent levels deep).

Modern code editors can deal with both types of indents equally, so the programmer who uses 4 space indents would still hit the Tab key to insert 4 spaces at a time, and backspacing the indent would backspace 4 at a time. The real difference just comes down to what actual characters are used for the indent (a single tab character or four space characters).

My rule of thumb

My rules for determining which type of indentation I use:

  • By default, I indent with tabs.
    • Perl, HTML, JavaScript, Java, ..., most programming languages.
  • But, if a programming language already has a strong style guide that prefers spaces, I conform to the style guide.
    • Python's PEP8 style guide says to use 4 space indents.
    • YAML and CoffeeScript developers have largely standardized around 2 space indents.
    • For Markdown I use 2 spaces as preferred by GitHub Flavored Markdown.

I won't make arguments about "a tab is only one byte but spaces are 4 bytes" because that doesn't really matter and it's all the same to the compiler. Instead my arguments are for what the developer experience is when writing code and collaborating with others.

So, why tabs?

First, let's start with a "conversation tree" on how the debate over tabs vs. spaces would play out:

  • Are tabs or spaces better?
    • Use tabs. Done.
    • Use spaces.
      • Okay, how many spaces?
        • 2 spaces
        • 4 spaces
        • 3 spaces (this is my troll answer when I get to this question)

The question isn't only about tabs or spaces, it's also about how wide do you want your indent to appear in your editor? This is the "2 vs 4 vs 3" space debate listed above.

Indent Width

If you use tabs to indent, then every programmer can use their own preferred indent width. If you like your code to use 4-space indents, you can just configure your text editor to show a tab as equivalent to 4 spaces. If your coworker next to you likes 2-space indents, he can have tabs appear as 2 spaces. If that weird coworker who likes 3 spaces wants to work on your code, he can show tabs as 3 spaces. Everyone gets what they want.

If you use spaces to indent, though, you'll run into personality clashes if two developers have different preferences on the indent width. I've seen GitHub pull requests where somebody reformatted the entire file to change the space count for indents to fit their own personal preference, only for the next pull request to change all the spaces back because the next guy liked them how they were.

What about lining up your source code?

A common argument I hear from the Spaces Camp is that you can't line up your source code all nice and pretty unless you use spaces all throughout. For example:

func CreateNewUser(username string, password string, first_name string,
                   last_name string, is_admin bool) *User {
    // Create a user object.
    user := &User{}

    // Call another function with a ton of parameters.
    user.InitializeDefaults(username, password, first_name,
                            last_name, is_admin,
    )

    user.Save()
    return user
}

In this example, the argument list for CreateNewUser() is so long that I had to span multiple lines of code, and I lined up the second line so all the arguments look nice and tidy in my editor.

Later, when I call user.InitializeDefaults(), I do something similar.

The Spaces Camp would say that if you indent with tabs, then a programmer is likely to hit "tab-tab-tab-tab-space-space" to get roughly to the right indent level and use spaces for the last couple inches, so that the code looks good on their screen. But then when somebody who has a different indent width configured (e.g. 2 spaces instead of 4) views the code, the formatting is all messed up:

func CreateNewUser(username string, password string, first_name string,
       last_name string, is_admin bool) *User {
  // Create a user object.
  user := &User{}

  // Call another function with a ton of parameters.
  user.InitializeDefaults(username, password, first_name,
              last_name, is_admin,
  )

  user.Save()
  return user
}

There's an easy solution to this that still works with tabs:

  • Indent using tabs to the same indent level as the first line of code,
  • And then use spaces to line things up.

Here's a short example of what I mean. I'm showing invisible characters in this example, so the represents a tab character and a · represents a space character.

func Example(username string,
·············password string) {
→   // function body indented with tabs per usual.
→   if os.Getenv("DEBUG") != "" {
→   →   fmt.Println("Debug mode enabled")
→   }

→   // But when you need to line stuff up, you tab to the same level as
→   // the parent line of code, and space to line up from there.
→   SomeLongFunctionCall(username,
→   ·····················password)
}

Now you can still perfectly line up code when needed, and if your coworker uses a different indent width than you do, everything is still lined up! In the example above I'm using a 4-space indent width, and if you changed the width to 2 spaces:

func Example(username string,
·············password string) {
→ // Hello, world.
→ // function body indented with tabs per usual.
→ if os.Getenv("DEBUG") != "" {
→ → fmt.Println("Debug mode enabled")
→ }

→ // But when you need to line stuff up, you tab to the same level as
→ // the parent line of code, and space to line up from there.
→ SomeLongFunctionCall(username,
→ ·····················password)
}

Everything is still lined up.

tl;dr.

People have strong opinions on this and I don't expect to be able to convince anyone, but this is how I indent my code:

  • Tabs by default for all file types.
  • Spaces only for file types that have a strong style guide that suggests spaces.
  • To line up code: tab to the same indent level, and then use spaces for alignment.

But above all, Rule #1 is to use the existing coding style when you join a project. At work I usually have to use spaces for all the things because we code in Python and most people feel it's easier to also format HTML and JavaScript the same way, but for my personal projects, I follow my own rules.

Poor Man's ngrok

Noah Petherbridge
kirsle
Posted by Noah Petherbridge on Thursday, May 04 2017 @ 10:23:08 AM

Recently, I was developing a Python/Flask app to implement Web Hooks for a third-party API that I was working with. The API recommended the use of ngrok during local development so that the server running on your local computer could be accessed publicly over the Internet (so that their API could reach yours).

ngrok is cool and all, but for their free plan they randomize the subdomain they give you every time you start the program. This meant I always had to log into my API account and change my Web Hook URL each day.

What ngrok is doing is nothing new: I've written about using SSH to forward ports between machines, and figured it should be easy enough for me to configure a subdomain on my own server that forwards traffic to another port that I could open when I need to.

I run the NGINX web server, so I just added some configuration for a subdomain that forwards all traffic to the local port 5000 on the web server:

server {
    server_name tun.kirsle.net;
    listen [::]:443 ssl;
    listen 443 ssl;

    ssl on;
    ssl_certificate /etc/letsencrypt/live/tun.kirsle.net/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/tun.kirsle.net/privkey.pem;

    ssl_session_timeout 5m;
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers 'EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH';
    ssl_prefer_server_ciphers on;
    ssl_session_cache shared:SSL:10m;
    ssl_dhparam /etc/ssl/dhparam.pem;

    # So the Let's Encrypt Acme client can use the webroot method
    location /.well-known {
        alias /var/www/html/.well-known;
    }

    location / {
        proxy_pass http://127.0.0.1:5000/;
    }
}

server {
    server_name tun.kirsle.net;
    listen [::]:80;
    listen 80;

    location / {
        proxy_pass http://127.0.0.1:5000/;
    }
}

This makes my tun.kirsle.net forward to localhost:5000 on the web server. When this port isn't currently bound to a remote SSH connection, nginx will return "502 Bad Gateway"

All that's missing now is a convenient client-side command to start/stop the tunnel when I want. For that, I added a function to my .bashrc:

# Poor Man's ngrok
tunup() {
    port=${1:-5000}
    echo "Forwarding kirsle.net:5000 to local port $port"
    ssh -R 5000:127.0.0.1:$port kirsle
}

So I can just run tunup to open the tunnel and close the SSH session when done. (I know there's a way to start SSH in the background/without an interactive shell, but I prefer to keep a shell open so I know when it gets disconnected). If I'm using a local port on my computer other than 5000, I can run e.g. tunup 8080 for the local port number.

RiveScript's Original Niche

Noah Petherbridge
kirsle
Posted by Noah Petherbridge on Friday, February 24 2017 @ 03:24:52 PM

I wrote this article for the RiveScript Community Wiki, but am reposting it here for visibility.

I've been noticing more and more lately that people are using RiveScript to power Facebook Messenger chatbots, which adds a whole lot of complexity that RiveScript wasn't ready for. This article explains why RiveScript was designed the way that it is, what it's doing to support modern chatbots, and recommendations for how to design a modern chatbot.

Read more...

On iOS Vulnerabilities

Noah Petherbridge
kirsle
Posted by Noah Petherbridge on Wednesday, January 18 2017 @ 12:59:09 PM

It seems there's a new iOS vulnerability where receiving a certain text message can crash your phone (forcing a reboot), and then lock you out of the Messages app--presumably because attempting to display the offending message will crash the phone again. Also, apparently, you don't even have to read the text message; the notification for the message alone will crash the phone too.

I heard of it from this article on Cult of Mac, and I have various thoughts on the matter (and about iOS vulnerabilities in general and how people handle them once discovered--the long story short is they're handled very poorly).

The article mentions that if you found yourself a victim to this exploit, you can "fix" it by visiting a web page in Mobile Safari which then offers to "Open this page in Messages" and then finds some way to allow safely deleting the text without crashing the phone.

I tried inspecting the source code of the "fix" page with the curl command line HTTP client (because you should never check out a possibly shady web page in your normal browser, as they might try and exploit some zero-day vulnerability in your browser and compromise your computer). But, it seems that the domain the fix was hosted on no longer exists: it gave me some DoubleClick "inquire about this domain" nonsense and tons of advertisements.

Either this is an extraordinary coincidence that the site is down now (given that the article was written today, and presumably the site worked when the author wrote the article), or the site was up to something shady and got reported and terminated by its host/registrar. My guess is that it was basically a jailbreak exploit, as iOS tends to be very locked down compared to Android (for example, no "Intents" system for apps to communicate with each other, and iOS doesn't allow replacing the default Messages app for managing your text messages).

Which brings me to how iOS vulnerabilities are handled in general by the users: very badly. Somebody discovered that they can crash iOS by sending a certain text message to an iPhone user, and instead of doing the responsible thing of privately informing Apple about it and not disclosing it publicly, they make YouTube videos being like "Text your friend these 3 characters and crash their phone! It's hilarious! Fun prank!"

It's not a fun prank. Short of using a shady as fuck web page that probably gains root privileges on the phone in order to fix your Messages app, the other way to fix it would probably be to factory reset the entire phone.

To compare with Android, vulnerabilities get disclosed in vague terms, like "somebody can craft a special audio file and text you it", but with no specific details, and the users are more concerned with updating their OS to patch the problem as soon as possible; rather than being, "I can crash all my friends' phones! I know exactly how to do it because blogs and YouTube videos are telling me how; and I'll use it to 'prank' as many of my friends as I can before Apple can fix it!"

One reason I'm glad not to be an iPhone user. I'd have to unfriend people IRL if they intentionally abused such a dangerous exploit against me.

Categories:

[ 0 comments | Add comment | Permalink ]

New Server Layout

Noah Petherbridge
kirsle
Posted by Noah Petherbridge on Thursday, January 12 2017 @ 12:59:42 PM

It's been on my to-do list for a while, and I've finally begun the process of rearranging my personal servers.

I've always treated my personal servers like pets rather than cattle, usually only having a single server hosted somewhere that runs all of my things. Most recently this took the form of a single Digital Ocean VPS that I named ocean.kirsle.net and that costs $40/mo. for 4GB RAM, and it ran all sorts of things:

  • All my websites including kirsle.net and a lot of legacy sites that don't even point to my server anymore but that I still had the document roots for.
  • Various random PHP apps, and a Git server, and other random nonsense.
  • My Minecraft survival server.
  • My self-hosted e-mail.
  • My XMPP chat server (until the server wouldn't start after a reboot one day).
  • My OpenVPN server (until that stopped working one day and couldn't be fixed due to the kernel not creating the /dev/tap device).
  • All sorts of random cruft on the filesystem, like custom-built local versions of Perl, Python 3.4 and Git.

The various sources of pressure that got me to finally start doing something about this include:

  • The OpenVPN server was unfixable and I'd need to start from scratch with a new VPS to re-implement it properly.
  • Digital Ocean added a Block Storage feature where you can attach a separately-growable disk to your VPS, but those are only available in certain datacenters so far. ocean.kirsle.net was hosted in SFO-1 but Block Storage was only available in SFO-2, so I wanted to eventually migrate to one of the data centers that supports this feature.
  • It took me all day long to configure my self-hosted e-mail, and I didn't wanna do that again too soon. First I tried setting up a brand new mail.kirsle.net VPS so that I'd only have to configure mail one more time, but it wasn't going very well so I decided to go back to Google Apps for my e-mail. I still have a grandfathered free account there, anyway.

So, now I have a new server named web.kirsle.net that's only $10 for 1GB RAM and it hosts all my simple websites, including this one! It took about four hours to migrate all my websites over, and in the process I also stopped hosting many random things. Like I don't use Piwik Analytics anymore (a PHP app), nor do I host a Git server now. The new server is so much lighter than the old one for it. I don't even have PHP installed, or Apache either.

My Minecraft server will be moved to its own VPS shortly, before I finish decommissioning ocean.kirsle.net. I'll eventually add more servers when I need to in the future too, e.g. to have a dedicated server for databases.

Categories:

[ 0 comments | Add comment | Permalink ]