Category: Adventure On

  • Mind the Gap: Reflections from a technical interview

    Last week, I had my first ever real-life technical interview. While I have been working as a software engineer for 10+ years, I came up through the ranks as a freelancer and then took on my first corporate role from within Automattic’s ranks. Automattic’s process was more trial-by-project rather than a one-hour technical interview.

    Photo by AXP Photography on Pexels.com

    I won’t know the results of the interview for a bit yet, but I wanted to reflect on areas I should be more mindful of in the future:

    1. Environment
      The invitation to the interview said I should set up a boilerplate web application environment using the tools and languages I was most comfortable in. I decided to install MAMP and spin up a vanilla WordPress install. Then I created a child theme and boiler plate plugin to be able to inject custom code for working through the exercise.

      There were pros and cons to this setup. For example, having the WordPress install meant that creating a new database table was as simple as coding a $wpdb query. Same with fetching or inserting new rows. However, some of the do-it-the-WP-way nuances also made the process more cumbersome. In the case of the database tables, actually building the new table required that I also had to write a function which would run the CREATE SQL command when the plugin was activated…and then call that function in the activation hook…and then go into plugin settings to deactivate/reactive the plugin to make it work (this could have been slicker if I had WP-CLI installed).

      I think there are definitely roles where having a WordPress install via MAMP would be a good solution, but I will also be testing out different boilerplates to get comfortable with a more streamlined version.
    2. Stay calm and think out loud(er)
      All week, leading up to the interview, I kept reminding myself to stop, read through the problem which the interviewer presented, write out an outline, explain my approach, and then dive into the code. I even had a Google Doc set up for that express purpose.

      So what did I do day of? Got nervous and jumped directly into the code 🙈. Luckily, the interviewer pulled me back and said, “Hey, maybe we should chat about this?”

      Throughout the pairing session, I also should have been more vocal about the choices I was making and the code I was creating. While I did bring up key decisions and pivots, it was easy to slip into silence because we were screen sharing. This gave a false sense that the interviewer could understand my more nuanced intentions and thought processes because they could read my code. However, as many of us already know, explicit is better than implicit. I should be saying what I am thinking in order to make sure the interviewer is following along with my process.
    3. Think deeper about greenfield solution design
      I don’t have much experience in building greenfield, blank-cursor apps. My work has generally always been built on legacy systems, existing APIs, or within the parameters of a pre-built ecosystem. I’ve been doing remodels and this interview was a new construction build.

      In this interview, I was too quickly focused on the how to make the application work right now. I skipped over thinking about how to make the application function throughout the long-term lifecycle of the product.

      The experience made me realize that I need to be more thinking through the architectural constraints of a project sooner. For example, if you need a unique ID to be created in a project, how many unique IDs will you need over the lifespan of the application? How long should the ID be in order to assure you never run out of unique combinations? The benefit of using WordPress is that it has build in functions, like wp_unique_id() which handle creating unique IDs. However, I think it would have benefitted the process to talk through the reasoning in a more in depth way.
    4. Be more curious
      At the end of the pairing session, the interviewer asked if there was anything I would have been concerned about when rolling out an application like we working on. My answers were performance and use case. Both of these are topics which would have been beneficial if they came out earlier in the conversation. the whole process would have been better if I had held onto my curiosity about why the company needed this feature and how they intended to use it. This could have informed the design phase and made implementation more robust.
    5. Practice makes perfect(ish)
      Guess who now has a Leetcode account? Yep, this lady. Exposing myself to the types of challenges and questions that technical interviews are likely to use will be how I translate my real-life skills into a successful interview.

    Have any pro-tips or suggestions for future interviews? I would love to hear them in the comments.

  • Get GOing; learning Go continued

    Picking back up with learning Go. Starting with Arrays.

    I have to admit, this will be a hard switch from PHP. When I create arrays in PHP, generally, I try to be as explicit as possible by using associative arrays so I can pull the information I want back out:

    <?php
    
    $new_array = array(
       'name' => 'Jane',
       'favorite_color' => 'Red',
       'favorite_fruit' => 'Apple',
    );
    
    ?>
    

    It doesn’t appear that Go has this option. If I wanted to create the same array in Go, it would look like this:

    package main
    import ("fmt")
    
    func main() {
      var new_array = [3]string{'Jane','Red','Apple'}
    
      fmt.Println(new_array)
    }
    

    Coming from a PHP perspective, this seems complicated because it means that I have to remember the order in which I input information to get the correct data out. This is a hassel. In PHP, I just have to remember that if I want a name, I would fetch $new_array['name']. In Go, this would have to be new_array[1] which isn’t as explicit.

    However, I think the reason behind this limitation is, in itself, a concept switch about how to use arrays. In PHP, arrays are a veritable Mary Poppins bag which can handle multiple data types, be deeply nested, and have items added or removed without consequence (until you need it). However, this can be a trap. If you haven’t thought through the structure of your data (for example, by creating a class object), it can be tempting to just throw everything in your array-bag. Longterm, this can cause your code to spaghetti out of control and be a performance pain.

    Go seems to seek to avoid this trap by requiring that all items in an array be of the same data type. There is also a built in method to control how many items should be placed within an array. In this way, arrays seems to be more like glass recycling bins in Germany.

    The bins only take one type of material, glass, it is sorted by color, and you can see when the bin is full. It doesn’t matter as much what order things are in because you know everything in the “brown glass” bin is a type of brown glass.

    Photo credit https://www.archer-relocation.com/how-to-recycle-in-germany/

    package main
    import ("fmt")
    
    func main() {
      var zipcodes = [3]int{ 10011, 90210, 20001 }
      var colors = [3]string{ 'red', 'blue', 'green' }
      var fruits = [3]string{ 'apples', 'oranges', 'cherries' }
    
      fmt.Println(zipcodes)
      fmt.Println(colors)
      fmt.Println(fruits)
    }
    

    This is the key point of the concept switch; in PHP arrays can be used as a catch all (even if it isn’t necessarily a good idea) and in Go arrays are used to catch a specific type of data with a grouped theme so you don’t care as much about the order of items. It almost seems like in PHP, it is easier to focus on where you need a collection of data because you can push so much into a single array (e.g. I am going to throw everything in my bag so I have it with me). In Go, a dev is required to think more about what you need since it makes more sense to group themed items together (e.g. I am going to use suitcase cubes to make sure its nice and neat).

    PHP Arrays

    <?php
    // Let's describe Mary Poppins in an array
    
    $mary_poppins = array(
       'height' => 'average',
       'singing' => 'often',
       'bag_items' => array( 'lamp', 'umbrella', 'spoonful of sugar'),
       'cleanliness' => 'strict',
       'hair' => 'brown',
    );
    ?>
    

    GO Arrays

    package main
    import ("fmt")
    
    func main() {
       // Let's describe Mary Poppins in a series of arrays
      var physical_traits = [2]string{ 'brown hair', 'average height'}
      var talents = [2]string{ 'cleaning', 'singing'}
      var bag_contents = [3]string{ 'lamp', 'umbrella', 'spoonful of sugar'}
    }
    

    Now, I don’t want to give the impression there isn’t any structure to Go arrays. It is based on the index of the array (which starts at 0).

    For example, let’s say you are running a competition. The competitors have to face off in a way that you know who got last place before you know who got first. Let’s set up an array to store where each competitor placed:

    package main
    import ("fmt")
    
    var winners_names = [6]string{}
    
    func main() {
      fmt.Println(winners_names)
    }
    

    Calling main() would result in printing [ "" "" "" "" "" "" ]. It is an empty array of strings. Now, let’s add a function to update the winners names as we receive them.

    package main
    import ("fmt")
    
    var winners_names = [6]string{}
    
    func main() {
      fmt.Println(winners_names)
    }
    
    func record_winner( place, name) {
       // Remember that indexes start with 0, so 6th place would actually be stored at winners_names[5]
       var index = place - 1 
       winners_names[index] = [name]
    }
    

    I think this syntax would work (haven’t got to writing functions yet 😅). So, when we found out that the first two competitors won 5th and 6th place, we could call record_winner() to update the winners_name list.

    record_winner( 5, 'Jane Doe')
    record_winner( 6, 'John Doe')
    

    Now, when we call main(), the output would be [ "" "" "" "" "Jane Doe" "John Doe" ]. You could repeat this until all of the placements are filled in.

    So this is really useful when you have a situation where the ranking of an item can match the order in which it needs to be displayed.

    Stopping here for today. I’ll pick this up next week with how to slice and dice things.

  • Calling for Backup

    I am currently working on a project to convert a list of active purchases from a custom React component to a built in WordPress component, DataViews. The goal of the project is to utilize the built in sorting, filtering, and pagination of DataViews to improve the user experience by making finding specific subscriptions in their account.

    GitHub Issue: https://github.com/Automattic/wp-calypso/issues/86616

    A big part of this project has been detangling functionality from class components so that the logic could be exported from the original PurchaseItem component to render within the columns of the DataViews table. I went through this process for each of the four columns in these PRs:

    Looks like I missed a notice when rendering the payment method. In the original PurchaseItem component, not only does the payment method type render, but also a notification about if there is a backup payment method.

    Production

    WIP DataViews

    While I was working on the CSS for the DataViews table, I realized that the notice was missing. Exporting the function for the notice and adding it to the payment-methods column wasn’t a problem. However, determining if the component should be rendered threw me a curveball.

    The parameter that determines if the component should be rendered is a const isBackupMethodAvailable. However, the value of this const isn’t available within the Purchases.Purchase type. It is actually set and passed to the PurchaseItem component one step higher in the chain, within the PurchasesSite component.

    { purchases.map( ( purchase ) => {
    	const isBackupMethodAvailable = cards.some(
    		( card ) => card.stored_details_id !== purchase.payment.storedDetailsId && card.is_backup
    	);
    
    	return (
    		<PurchaseItem
    			{... A Bunch of Props...}
    			isBackupMethodAvailable={ isBackupMethodAvailable }
    		/>
    	);
    } ) }
    

    At face value, this isn’t a hard fix. I could copy this logic setting isBackupMethodAvailable over to the PurchaseItemRowPaymentMethod component within the DateViews fields. This would allow me to check the two conditions for showing the notice:

    • Is the current payment method different from the one being used for the purchase?
      • We need the current payment method to be different from the payment method assigned to the purchase. If it isn’t, then the payment method isn’t really a back up–its just the payment method assigned to the purchase. Even if the current payment method is marked to be used as a backup for other purchases, if there isn’t a secondary backup payment method available, then this purchase doesn’t have a backup available.
    • Is the card set to be used as a backup payment method?

    If these two conditions are true, then there is a valid backup payment method and the notice is shown.

    The tricky part is when you start thinking about performance and scale. If a user only has a few purchases and one or two payment methods, then copying this logic over may not be a big issue and the user likely won’t see any drag.

    However, if the user has hundreds of purchases and lots of payment methods, this could cause a problem. The DataFields API renders one item at a time. Currently, the payment-method field uses the purchase as the item to render. Since this type doesn’t include the backup payment method information, I can’t just pass it in. So the approach of just copying and pasting this logic over would mean that for every row of the DataViews table, I would need to fetch all of the customer’s payment methods, filter through them to see if there are payment methods which are a backup but not assigned to the purchase, and then repeat it again in the next row.

    See how that could get expensive really quickly?

    To avoid this performance drain, I have two other options:

    1. Shove the isBackupMethodAvailable const into the Purchases.Purchase type
    2. Fetch the array of payment methods only once and pass it to getPurchasesFieldDefinitions via the usePurchasesFieldDefinitions hook

    I don’t love adding a parameter to a type for a single use case. So I am going with option 2.

    The first step is to pass the array of payment methods (StoredPaymentMethod) to the hook from the purchases list index file. This is the closest place where we have this context already available.

    // purchases-list-in-dataviews/index.tsx
    
    <PurchasesDataViews
    	purchases={ purchases }
    	translate={ translate }
    	paymentMethods={ this.props.paymentMethodsState.paymentMethods }
    />
    

    We then pass the data from the DataView, to the usePurchasesFieldDefinitions hook, and then finally to the getPurchasesFieldDefinitions function.

    // purchases-data-view.tsx
    
    export function PurchasesDataViews( props: {
    	purchases: Purchases.Purchase[];
    	translate: LocalizeProps[ 'translate' ];
    	paymentMethods?: <Array>StoredPaymentMethod;
    } ) {
    	// ...Other logic...
    	const purchasesDataFields = usePurchasesFieldDefinitions( paymentMethods );
    	// ...More logic...
    }
    
    // use-field-definitions.ts
    
    export function usePurchasesFieldDefinitions( paymentMethods) {
    	// Do some stuff with the payment methods
    	// Other hook logic
    }
    

    Now, remember, there are two conditions for a purchase to be considered as “having a backup payment method”. The first is that the id of the StoredPaymentMethod for the purchase cannot match the backup method, and the second is that the StoredPaymentMethod has to be set to be a backup.

    Let’s flip the order in which those items are checked. This way, we can send only the StoredPaymentMethods which are a backup to getPurchasesFieldDefinitions. When each row compares the payment method assigned to that purchase, it will have a smaller scope of payment methods to test against since we have already weeded out any payment methods which aren’t meant to be used as a backup.

    const backupPaymentMethods = paymentMethods.filter(
    	( paymentMethod ) => paymentMethod.is_backup === true
    );
    

    Once we pass that back to getPurchasesFieldDefinitions, we can now render the backup payment method notice by checking if the current purchase’s payment method is the only payment method found in the backupPaymentMethods array.

    let isBackupMethodAvailable = false;
    
    if ( backupPaymentMethods ) {
    	backupPaymentMethods.filter(
    		( paymentMethod ) => item.payment.storedDetailsId !== paymentMethod.stored_details_id
    	);
    
    	isBackupMethodAvailable = backupPaymentMethods.length >= 1;
    }
    

    Now, we have checked for both conditions and saved ourselves from having to fetch a set of payment methods multiple times.

    Thoughts? Comments? Concerns? Have a better way to address the problem? Let me know in the comments.

    If you want to see the full work in progress, check out the PR here: https://github.com/Automattic/wp-calypso/pull/102742

  • Lets GO!

    I am by trade and tutelage, a PHP developer. I can also work in JavaScript, TypeScript, and React. The bulk of my work has been done in PHP. As someone pretty heavily entrenched in WordPress ecosystem, this served me well.

    However, as I am looking to new horizons, it is becoming apparent to me that I will need to stretch my language skills even further. So let’s start with Go. Please enjoy my ramblings as I learn 🙂

    I am starting with a really basic w3schools.com tutorial to see what the differences in syntax are.

    A Go file consists of the following parts:

    • Package declaration
    • Import packages
    • Functions
    • Statements and expressions
    https://www.w3schools.com/go/go_syntax.php

    From a PHP perspective, this sounds pretty similar.

    • Package declaration => PHP Namespaces. It gives the program or file scope or limitations
    • Import packages -> In PHP, this was done by the use statements. It allowed you to pull in functions from other namespaces, classes, or even just a single function
    • Funcations => This is pretty self explanatory
    • Statements and expressions => I am intrigued…

    Syntax

    In Go, statements are separated by ending a line (hitting the Enter key) or by a semicolon “;“.

    Hitting the Enter key adds “;” to the end of the line implicitly (does not show up in the source code).

    https://www.w3schools.com/go/go_syntax.php

    ❓ Does this mean there are hidden semicolons throughout the code, or does the complier read a new line as a semicolon?

    The answer seems to be yes, under certain conditions. I guess for now, I will continue to explicitly write out my semicolons to avoid confusion until I am more comfortable working in Go. Also, to avoid causing myself grief when I switch back to other languages.

    Comments

    • Single line comments start with //
    • Multiline comments are encased in /* {your comment here} */

    Creating variables

    • Use var
      • Benefit: this allows you to specify the type of the variable (e.g. var test string = "some words";)
      • This notation can be used within a function or without
      • Allows for value assignment to be done separately from declaration
      • Always requires at least a type or value
        • var test string = "string";
        • var test string;
        • var test = "string";
        • var test;
    • use :=
      • This is a new notation to me, I would have easily mixed it up for a logic statement if I had run into it in the wild
      • Benefit: It is a shorthand and so it may be faster to use
      • Like Typescript, the compiler infers the type of the variable based on the value
      • This notation can only be used within a function
      • Value must always be assigned at declaration
      • It is not possible to declare a variable without a value using this notation (which makes sense)
        • test string := "string"; (I think this would set a variable string as "string")
        • test := null; (null and empty strings both throw an error)
        • test := "string";
    • Declaring multiple vatiables
      • Similar to a mathematical matrix, you can declare multiple variables and values at the same time
        • So, for example var a, b = 6, "Hello!"; is the same thing as declaring var a = 6; var b = "Hello!";
        • If you declaring multiple variables at the same time only supports one type
          • var a, b string = "A", "B";
          • ❌ var a string, b int = “A”, 2;

    Go variable naming rules:

    • A variable name must start with a letter or an underscore character (_)
    • A variable name cannot start with a digit
    • A variable name can only contain alpha-numeric characters and underscores (a-z, A-Z0-9, and _ )
    • Variable names are case-sensitive (age, Age and AGE are three different variables)
    • There is no limit on the length of the variable name
    • A variable name cannot contain spaces
    • The variable name cannot be any Go keywords
    https://www.w3schools.com/go/go_variable_naming_rules.php

    Variable names support camel case, pascal case, and snake case.

    Constants

    Seems constants work as expected, they should be declare once, are unchangeable and read-only, can be typed or have the type inferred from the value. To make constants easy to identify, they should be written in uppercase letters (e.g. “USER_AGE”, not “userAge”, or “UserAge”, or “user_age”)

    Output

    • Print()
      • Prints out the value only, can have multiple comma separated arguments passed in (e.g. Print( "Hello", " ", "World");) Reminds me of the Google Sheets function Concatenate.
    • Println()
      • Adds whitespace between arguments and new line at the end. So Println( "Hello", "World"); prints out Hello World.
    • Printf()
      • Allows integrating the type or value of a variable into a string.
    package main;
    import ("fmt");
    
    func main() {
       var userName string = "Jane";
    
       fmt.Printf( "Hello %v!", userName ); 
       // Prints "Hello Jane!"
       fmt.Printf( "The name %v is a %t", userName, username ); 
       // Prints "The name Jane is a string"
    }
    

    The values are integrated into the string using formatting verbs.

    I’m stopping here for today. The next item up in the tutorial are arrays, and they are definitely different than how they work in PHP. I’ll save that for next time.

  • TIL: Dropping commits and search replace

    I have been working on a project where my access rights to the original repo have recently changed. Before, I was able to push directly to the repo. Now, I am pushing changes as an OSS Citizen and making pull requests from a fork.

    This means that I needed to pull my original WIP branch into my local fork, continue making changes and then push it to a new pull request.

    However, as I was trying to keep things up to date, a rebase from trunk polluted my forked PR with ~260 commits which I didn’t need 🙈

    Stackoverflow to the rescue! I found a similar question which suggested running git rebase -i HEAD~n.

    When I ran git rebase -i HEAD~270, vi opened the file with the listed commits. That’s when reality hit. I would need to make ~260 edits to change each pick to drop. There had to be a better way.

    There is! vi has a search and replace function.

    To search and replace for a single instance of a string run this: :s/search-key/replace-key. It will replace the first instance in the file.

    To search and replace for multiple instances of a string, add in %

    The command now becomes :%s/search-key/replace-key

    So in my case, I ran :%s/pick/drop

    This allowed me to mark all of the commits as dropped and then go back and manually select the 10ish commits I wanted to keep back to pick.

    I finished up the process by running git push --force (you could also use --force-with-lease).

    Now my PR is cleaned up and ready for review.

    Something to consider for the future, if I dropped a commit I actually needed, how would I get it back?

  • Farewell A8c

    On Wednesday, my role at Automattic was included in the workforce restructuring which was announced. I was separated from the company.

    While I don’t have much to say about how I was separated from Automattic, I have so much gratitude for my time with the company. More than I could ever fit in a single post. Automattic has been a support through some of the biggest transitions of my life. I became a mom, had a second child, stepped fully into being a developer, and moved across the United States. So much would have been drastically different about those experiences without the support of working at Automattic.

    More than the support of the company, I am grateful for the connections I have made. There are so many people and precious memories which I have from the last 6 years.

    Hobbes welcomed me with open arms and showed me the ropes. I am so thankful for each one of you 🍓 🍓 🍓

    Shilling, thank you for welcoming me and raising me up from a baby dev to where I am today. I know I have said most of this to each one of you, already, but I learned so much from you all. Not just how to write code, but how to be a better human. I hope that when you find unintentional easter-eggs, it brings a smile to your face. Thanks for always being willing to go along with my crazy meetup schemes. You were the best for playing along.

    There are so many people, teams, and orgs of Automattic that impacted my time there. I wouldn’t be able to name them all without forgetting someone, so I will just stop here.

    If I could offer any advice to everyone affected by this change, it would be to only hold on to what serves you. There is a process to grieving, but don’t get stuck in it. Take the good things forward, the lessons, the memories, the connections. Let the rest fall behind you when they aren’t helpful anymore.

    So what’s next? My ideation has kicked up a few things:

    • Write a novel about a certain dev team which gets sucked into the world of Fae and saves it because writing code isn’t that different from writing spells, right?
    • Start an online bartering marketplace and make money selling shipping labels
    • Create a meetup consultancy to plan retreats for groups of 5 – 14 people

    If your interested in staying in touch, feel free to contactme[at]jessboctor.com.

  • Progress is messy.

    Right now, my living room looks like a baby GAP threw up in it.

    It has for a week.

    I started a project to clean out and organize our closets. As part of that project, I washed all the laundry and plopped it on our coach. I dragged all the clothes that my kids have outgrown and put them in trash bags in the entryway (so I remember to take them…somewhere). I am also sorting through the mountain of clothes that my kids are growing into, so their closets are updated. I don’t want to accidentally buy the same thing twice again (yes, it has happened).

    This is a whole day project in the best of times. Between work, family obligations, and health issues, this has turned into a two-week project.

    My living room has been in this chaos for two weeks. I hate it. My husband hates it. The dog loves it. There are, after all, extra snuggies on the coach.

    The most frustrating part is the mess. It feels like the more progress I make, the messier the living room gets.

    This week, I had to take a breath and remind myself that it’s okay, because progress is messy.

    Want to get rid of the junk in your trunk? You have to drag it all out.

    Want to learn a new skill? Prepare to tear your hair out, have notes, and learning things, and mental models, and mess everywhere.

    Want to heal from your childhood trauma? Bad relationship? That mental health issue that has plagued you since you were a teen? Prepare for a snot-nosed, emotional mess.

    The good news is that the mess doesn’t have to stay. It can get cleaned up, picked up again, and put back in order.

    The process of progress, though, it is a messy one.