Thursday, January 16, 2020

My First Coding Project: A Ruby CLI Application

I was a little nervous getting started with Flatiron's Coding Bootcamp. I knew that I enjoyed coding, but I had also been teaching the same introductory content for years and didn't know how I would feel once I left that little bubble. After completing my first project, I can say that it is an incredible high to have designed and written a program that uses a host of tools and concepts -- object models, interacting classes, a command line interface, data from an API -- and that someone else could legitimately use! It's a far cry from being "useful" in the real-world sense, and still leaves me with this gnawing question of "will I actually be able to create things that people are willing to pay me for?" but I do feel that this project was a big step in the positive direction. Here are some reflections on the process.

Using an API: Imposter syndrome is always an issue for me, and it is often triggered by hearing terms over and over that seem to mean something specific to everyone else but are confusing to me! Once of those terms has always been "API" -- I would hear those letters thrown around so casually and think that I must be the only one who doesn't actually understand what an API is. This project was a great way to tackle that head-on. My awesome instructors demystified the idea of an API as simply a way of communicating with a database, and modeled the process of playing with various API requests and the data they returned. I used this list of public API's to guide my research and wound up choosing Open Trivia Database's Trivia API.

Starting with the End Product: In teaching we call this "backwards planning", and I found it to be great advice in terms of getting past that blank-screen syndrome. Instead of starting by mapping out all of my classes and methods, I started high-level: by creating my command line interface. What did I want the user to see when they pressed 'run'? When they pressed 'play', what menu should appear? As I thought about this from the perspective of the user's experience, the necessity for certain methods just became super clear. (I love the idea of calling the methods you wish you had, and then going back and implementing them!) Once I had something working, I was able to go back and refactor my methods ... but it was much easier to think calmly and deeply about my program's structure when I already had some working code.

Creating Object Models: In previous labs, we had been told what attributes and methods our objects should have. This time, it was up to us to design our classes. It made sense to create TriviaQuestion objects out of the trivia question data I was retrieving. Most of my attributes corresponded with information I was pulling directly from the API request: every trivia question had a category, a difficulty, a question, etc. I had to get a bit more creative in terms of storing a TriviaQuestion's answer choices. The API returned a question's correct answer as a string, and its incorrect answers as an array of strings. For example, the following question:


has a correct_answer of "1984", and incorrect_answers of ["Catcher and the Rye", "The Old Man and the Sea", "To Kill a Mockingbird"] ... which is all well and good, but doesn't really help when the user types in 'A' or 'C' as their answer choice! So, I decided to create an answer_hash attribute in order store the (randomized) letters themselves as data, and associate them with their corresponding strings. This particular question would have an answer_hash attribute of:


The previous data structure labs were interesting but had left me wondering why and how I would organically create such a structure (like a nested hash) on my own. Hence the epiphany when I came to this part of my project!

Testing for Bugs: After some playing around, I decided that five was the right number of questions for these mini trivia quizzes. I was having a lot of fun testing my program and answering trivia questions from various categories, and it wasn't until I had done this about 15 times that I happened to hit a category that didn't have five questions for the difficulty I had specified (I believe I was looking for hard questions about gadgets?). I referred back to the API documentation and noticed that "the API appends a 'Response Code' to each API call to help tell developers what the API is doing." How convenient! As a solution, I decided that I would generate as many questions as possible using the desired category and difficulty, and then fill in the remainder with questions from random categories of the same difficulty.

In future iterations of the program, I'd like to enable the user to select multiple categories from which to pull questions. For now, I think this is an effective work-around that prevents a somewhat subtle bug from causing some unexpected behavior!

Being OK with "Smile and Nod": As I continue to embrace the beginner's mindset on my coding journey, I can't help but think about phrases I used to repeat to my high school AP Computer Science students (most of whom were coding for the first time). One such instance was on the first day of class, when they'd write their first Hello World program in Java. To get this one statement to display to the console, students would have to declare a class and a main method using public static void main(String[] args). This was literally gibberish to my kids and I tried to encourage them by telling them this was a "smile and nod" moment: although they had no idea what things like public and static meant, it didn't matter -- they could still write cool code, and if they kept on learning then one day they would understand things like public and static.

As a highly linear thinker, it can be difficult to take this particular advice myself. I like to start at the beginning and understand everything fully, in order. As we know, this just isn't always possible. One particular "smile and nod" moment for me in this project was noticing that the API calls seemed be returning strings with some encoded symbols. I did a little digging and decided that the easiest solution was to ask the API to return the data using what seemed like an even deeper level of encoding (base-64), but one that appeared straightforward to ask Ruby to decode using the Base64 module. I understand very little about encoding and literally nothing about base-64, but I'm proud that I forged ahead and patched up the problem rather than allowing it to consume me.


No comments:

Post a Comment