Justin Joyce https://justinjoyce.dev/ Practical tips and tutorials about software development. Mon, 29 Jul 2024 12:24:42 +0000 en-US hourly 1 https://wordpress.org/?v=6.9.4 https://justinjoyce.dev/wp-content/uploads/2023/10/cropped-android-chrome-512x512-1-32x32.png Justin Joyce https://justinjoyce.dev/ 32 32 Every Built-In Vim Color Scheme (with screenshots) https://justinjoyce.dev/every-built-in-vim-color-scheme-with-screenshots/ Mon, 29 Jul 2024 12:24:41 +0000 https://justinjoyce.dev/?p=1793 I tried searching for a nice listing of all the built-in Vim :colorscheme (or :colo for short) options and I couldn’t find one, so here it is. This post is every :colo option that comes with Vim and a screenshot of what it looks like with some nonsense python code.

The post Every Built-In Vim Color Scheme (with screenshots) appeared first on Justin Joyce.

]]>
I tried searching for a nice listing of all the built-in Vim :colorscheme (or :colo for short) options and I couldn’t find one, so here it is. This post is every :colo option that comes with Vim1 and a screenshot of what it looks like with some nonsense python code.

  1. On my 2022 MacBook Air, anyway ↩

The post Every Built-In Vim Color Scheme (with screenshots) appeared first on Justin Joyce.

]]>
Reverse a string in Python https://justinjoyce.dev/reverse-a-string-in-python/ Thu, 08 Feb 2024 17:28:40 +0000 https://justinjoyce.dev/?p=1636 The simplest way is to use Python double colon slicing with a negative step value: You can also use the builtin reversed method, which will “Return a reverse iterator over the values of the given sequence” according to its official help(). Note that this returns an iterator, not a string, so you’ll have to do […]

The post Reverse a string in Python appeared first on Justin Joyce.

]]>
The simplest way is to use Python double colon slicing with a negative step value:

my_string = "abc123"
reversed_string = my_string[::-1]
print(reversed_string)
"321cba"
Python

You can also use the builtin reversed method, which will “Return a reverse iterator over the values of the given sequence” according to its official help(). Note that this returns an iterator, not a string, so you’ll have to do a bit more work to create the reversed string:

my_string = "abc123"
reverse_iterator = reversed(my_string)
# <reversed at 0x1051bead0>

new_string = ""
for char in reverse_iterator:
  new_string += char
  
print(new_string)
"321cba"
Python

You could also build a list from the reversed object and then .join it like so:

my_string = "abc123"
reverse_iterator = reversed(my_string)
# <reversed at 0x1051bead0>

"".join(list(reversed_iterator))
# "321cba"
Python

Both of these methods work equally well on lists.

The post Reverse a string in Python appeared first on Justin Joyce.

]]>
Meeting Cost Calculator https://justinjoyce.dev/meeting-cost-calculator/ Tue, 23 Jan 2024 07:13:00 +0000 https://justinjoyce.dev/?p=1770 How much do your meetings really cost? Find out here.

The post Meeting Cost Calculator appeared first on Justin Joyce.

]]>
Hello, very small number of people! Welcome to another small side project.

Everyone loves to complain about meetings, especially engineers. One day during a particularly rough one I thought, “How much does this meeting actually cost?”

So, contrary to the advice I would give to anyone, I ignored the many, many existing tools I found on Google and built my own. Those tools mostly are not very good on mobile (which I wanted) and I’ve been meaning to brush up on my React skills anyway, so two birds one stone.

As I was building it, it occurred to me that a more useful (and less complain-ey) engineering-specific version of this tool could apply to things like: test run duration, webpack build times, local development setup time, CI build duration, etc. In fact, those things are likely even more expensive than meetings! But by then I had mostly finished building it and time is precious; so here we are.

I’m sure there are some edge cases I haven’t accounted for, but feel free to play around with it at meetings.justinjoyce.dev.

screenshot of meeting cost calculator

The post Meeting Cost Calculator appeared first on Justin Joyce.

]]>
Vim find and replace https://justinjoyce.dev/vim-find-and-replace/ Sat, 16 Dec 2023 23:11:12 +0000 https://justinjoyce.dev/?p=1603 Easy find and replace is one of those quality of life improvements that make Vim great. With one quick command, and a little regex, you can replace a pattern across an entire file. Here’s how: Find and replace in the entire file with Vim:%s This is my most-used version of Vim find and replace. :%s […]

The post Vim find and replace appeared first on Justin Joyce.

]]>
Easy find and replace is one of those quality of life improvements that make Vim great. With one quick command, and a little regex, you can replace a pattern across an entire file. Here’s how:

Find and replace in the entire file with Vim:%s

This is my most-used version of Vim find and replace. :%s will find and replace a pattern across the entire file being edited. The command is formatted like this:

:%s/match/replacement/.

It’ll take anything in your file which matches the match pattern, and replace it with replacement.

For example, here’s a screenshot of some identifying strings I need to reformat:

a list of identifier strings
I need to remove these “AA.csv_” prefixes

With %s, we can remove all of those prefixes with one quick command:

a list of identifier strings with some highlighted
These have to go
final list of strings after replacement
The final result

This is the exact command I used: :%s/\w.+_//g. Let’s break it down:

  • :%s – this is the Vim command for find and replace on all lines.
  • /\w.+_/ – this is the matcher expression. It says “find a word character, followed by any number of anything else followed by an underscore “_”. Not very specific, but good enough for this job.
  • // – between these two slashes is what to replace with. In this case it’s empty because I want to delete.
  • g – Global flag. This makes sure the replacement affects multiple occurrences on the same line. By default, this command only changes the first instance on each line.

Important note: Pure Vim tends to interpret regex characters literally, so you may have to escape regex quantifiers. Meaning: instead of using + in your pattern you might need \+. So the full pattern above would become /\w.\+_/.

I ran this pattern (/\w.+_/) in pure Vim and again in VS Code with Vim emulation—pure Vim did require additional escaping, VS Code did not.

Find and replace with Vim :s

This works just like the :%s command, but :s will only replace on your current line.

regex highlighting one line

That’s about it for the basics. For a lot more detail, like how to search specific line ranges, check out this detailed post.


Helpful Links

The post Vim find and replace appeared first on Justin Joyce.

]]>
What makes an effective development team https://justinjoyce.dev/what-makes-an-effect-development-team/ https://justinjoyce.dev/what-makes-an-effect-development-team/#comments Wed, 01 Nov 2023 22:34:24 +0000 http://mdh.tqp.mybluehost.me/?p=540 After a decade as an engineer at companies ranging in size from 6 people to over 1000, I've been on many teams. The good teams were very similar, the bad ones were each bad in their own way. This post describes what made the good teams good.

The post What makes an effective development team appeared first on Justin Joyce.

]]>

Happy [development teams] are all alike; every unhappy [development team] is unhappy in its own way. – Tolstoy, if he delivered software1

After a decade as an engineer at companies ranging in size from 6 people to over 1000, I’ve been on many teams. The good teams were very similar, the bad ones were each bad in their own way. This post describes what made the good teams good.

Trust

Trust is the bedrock of any good team. You have to trust that your teammates know what they’re doing, that they hold themselves to the same standard you do, that they care. They don’t have to care care, they don’t have to live and breathe your company’s mission, but they do have to care about delivering quality work.

When there’s a production incident, you need to be able to trust that your teammate who is “on it” is actually on it. They may not be able to personally fix the problem, but they have to be able to handle the situation and get the right people involved quickly.

When there’s a bug, you need to be able to trust that whoever says they fixed it really did fix it. We’ve all encountered people/teams where you hear, “Oh that’s good to go, X fixed it,” and then no more questions are asked. The best team I’ve been on was comprised entirely of people like X.

Along with trusting your teammates’ work, you have to be able to trust them as people. I’ve spent most of my career working in some form of Agile, which means I’ve (mostly) had retrospective meetings after sprints or big projects. The best teams have really aired out their feelings in those meetings, because they trust that the rest of the team will listen to them and, if possible, help resolve the issue that’s bothering them. I’ve devoted entire sprints to work created by these discussions, valuable work, which might not have happened if my teammates didn’t feel comfortable speaking up.

Trust is hard. It has to be earned, and it’s easily lost2. But on every good team I’ve been a part of, it was crucial.

Respect

Respect and trust are tightly related, it’s hard to have one without the other.

Even on an effective team, it’s not always smooth sailing. Every person has their opinions, and we all think we’re right, especially engineers3. That means arguments are bound to happen. What I’ve noticed about good teams is that even if an argument gets heated, it’s always respectful. People may disagree intensely, but they don’t make it personal—they keep it focused on the work. In fact, these disagreements often shine a light on a more widespread misunderstanding, like: “Why exactly are we building this feature? Would our time be better spent elsewhere?” Your team needs to be able to have those conversations, and without respect they can quickly become unproductive.

Communication and Collaboration

In all relationships, communication is key. In the era of remote work, it’s even more important, and harder to do well. Being available to help talk through problems, or to screen share and pair program with someone is huge. The world of software engineering is vast, no one knows everything, but across the engineers on a team there’s a lot of knowledge. You have to be willing to ask, and your team has to be willing to help.

Asking for help is is something that newer engineers can struggle with. Knowing when you’re truly stuck is harder than it seems, but it’s a valuable engineering skill. If the junior people see others asking for (and receiving) help often, they’ll be more inclined to ask themselves. And they should! You can learn a lot more from a quick conversation with a knowledgable teammate than if you spent that same time struggling on your own—it’s how I learned many of the tips I wrote about in Developer workflow tips no one tells you about.

Humility

Above I mentioned being able to ask for help. Implicit in that is having to admit that you don’t know something, and haven’t figured it out, which requires some professional vulnerability. That’s why it’s important that everyone exhibits humility in their interactions.

A question might seem basic, and you might think “that’s kinda dumb, they should know this already”, but if you approach your interaction that way, the help-seeker will never ask you for help again, and the whole team will suffer as a result.

You might know better, but even if you do people aren’t going to want to work with you if they feel like they’re being talked down to.

Ownership and Accountability

Unless your company is quite small, it likely consists of multiple teams. It’s important that each team has concrete and distinct ownership over their respective domains. It needs to be clear who owns what; shared responsibility will always become someone else’s responsibility4.

Hand in hand with ownership is accountability. The people or team who own a domain should also be accountable for it. If my team owns the payments service, it should be our responsibility if something goes wrong. If there’s a payment-related incident, I would expect whoever is on call to reach out to us immediately.

Related: Blame and Post-Mortems

Implicit in accountability is the possibility of blame: “payments had an issue, it’s Justin’s team’s fault.” Now, that may be true, maybe we shipped some bad code. But it also might not be true, maybe a dependency is down. Either way, blaming us isn’t going to fix anything, it will just make us less inclined to ship anything at all in the future. You should always assume good intentions; no one is breaking things on purpose.

Rather than blaming, an effective team might ask questions like:

  • What exactly happened?
  • Were we notified quickly?
  • How did this make it past our testing?
  • How was it resolved?
  • How can we prevent this from happening again in the future?

The most effective team I’ve been a part of formalized this process into a mandatory blameless post-mortem meeting, held after any production incident. That meeting also had a single owner, and all resulting follow-up work was assigned to individuals before the meeting ended.

Things are going to fail, it’s inevitable. Even if your code is perfect (which it never is), your dependencies will go down. An effective team sees each failure as a chance to improve, not an opportunity to blame someone else.

Recruiting

Without the right people to start, nothing else in this post is possible. You need people with the right technical skills and attitudes, and one bad hire is enough to ruin a team. There’s a reason why Steve Jobs considered recruiting his most important job.

Recruiting is really hard to do well; it’s worth investing a lot of time into your recruiting and interview process. The best recruiting process I’ve seen was extremely well-defined: there were thorough rubrics demarcating performance expectations for each level for each module, interviewers were trained on the modules in advance, scoring was done independently, and then all people involved in the process had a wrap up discussion. This process might seem like table stakes, depending on where you’ve worked before, but trust me, it is not.

Meetings

Engineers are particularly sensitive to meetings. “Maker Time” has been discussed since at least 2009, and it is a very real thing. Writing quality software is deep work which requires long periods of uninterrupted focus. If it takes 30 – 60 minutes to get the full scope of a problem contextualized, but you have a meeting every 90 minutes, you won’t get anything done. If that happens often enough, you won’t even bother trying to get things done, because you know an interruption is coming.

The best teams I’ve been on acknowledged this explicitly, going as far as blocking out entire days of the week where individual contributors would not be added to any meetings. Granted, blocking the entire day might not be realistic everywhere, but at the very least teams should strive to only include people who are truly needed in meetings. Some flavor of the two-pizza rule can probably apply to most businesses.

A brief word on managers

I have never been a manager myself, so I’ll keep this section brief.

A good manager is a bit like your little finger. If everything is going well, you might hardly notice them. But as soon as something is wrong, it’s all you can think about.

People like to blame their managers for problems, but in my experience a bad manager is a symptom of larger problems at least as often as they are the cause, like a lack of trust, or a lack of organizational alignment5. The best manager I’ve ever had mostly left my team alone to do our thing, while protecting us from interruptions behind the scenes. He was always available if we needed him, but we had proven that we could reliably ship quality work, so unless we needed his input he just let us run, which we all really appreciated. The easier you can make your manager’s life, the happier everyone will be.

Conclusion

This isn’t an exhaustive list of criteria by any means, but it’s enough to see why it’s so hard to build effective development teams. If any of the attributes above is lacking, the team will suffer. It’s why people with titles like “Head of Engineering” or “CTO” get paid the big bucks. It’s their job to try and create an organization that does everything above well, and more. If they can do it, they’re worth every penny.


Notes

  1. The Anna Karenina principle has apparently been studied as it relates to success in science. TIL ↩
  2. There’s an old saying, “Trust arrives on foot but leaves on horseback” which I think captures this sentiment perfectly. ↩
  3. Tabs vs spaces, anyone? Vim vs Emacs? Maybe TS vs JS? ↩
  4. Diffusion of responsibility is a well-studied phenomenon ↩
  5. Organizational alignment could be its own section entirely ↩

The post What makes an effective development team appeared first on Justin Joyce.

]]>
https://justinjoyce.dev/what-makes-an-effect-development-team/feed/ 1
Check if key exists in Javascript object https://justinjoyce.dev/check-if-key-exists-in-javascript-object/ https://justinjoyce.dev/check-if-key-exists-in-javascript-object/#comments Mon, 30 Oct 2023 17:59:00 +0000 https://justinjoyce.dev/?p=1441 To check if a key exists in a js object, you can: compare the key's value to undefined, use .hasOwnProperty, or use "key in object" syntax. Each method behaves a bit differently, so let's do some examples.

The post Check if key exists in Javascript object appeared first on Justin Joyce.

]]>
To check if a key exists in javascript, you have a few options:

  1. Compare the key’s value to undefined
  2. Use object.hasOwnProperty
  3. Use Javascript’s “in” operator

Each method behaves slightly differently, so let’s do some examples of each.

Compare the key’s value to undefined

If you try to access a key that does not exist on a javascript object, the returned value will be undefined1. Let’s see that in code:

const car = { make: "toyota", model: "camry" };
car.make; // "toyota"
car.year; // undefined

In other words, if you access an object key and receive undefined back, that key was probably2 not present on the object.

if (car.year === undefined) {
  console.log("year is not present");
} else {
  // go ahead and use the year
}

If you have nested keys, however, you might run into issues:

const car = { make: "toyota", model: "camry" };

// This will result in an exception
if (car.details.mpg === undefined) {
  console.log("MPG not present");
}

// Uncaught TypeError: Cannot read properties of undefined

The TypeError in the example above is because the car object didn’t contain details. So car.details returned undefined, and you can’t do undefined.mpg. To prevent this issue with nested objects, use optional chaining ?..

Use optional chaining for nested keys

Continuing the car example above, let’s say you’re not even sure you have the car object itself. You can use javascript’s optional chaining operator (?.) to protect yourself.

const car = { make: "toyota", model: "camry" };

// This won't blow up
if (car?.details?.mpg === undefined){
  console.log("MPG not present");
}
// "MPG not present"

// You can chain as far as you like
if (car?.details?.dimensions?.length?.inches) {
  // this is safe from errors, but I'd probably
  // ask questions in code review if I saw this
}

Optional chaining will account for both null and undefined values in the chain of object keys, so it should keep you fairly safe.

Object.hasOwnProperty

The hasOwnProperty method was written for exactly this use case.

const car = { make: "toyota", model: "camry" };

car.hasOwnProperty("make"); // true
car.hasOwnProperty("year"); // false

Additionally, hasOwnProperty will ensure the property truly belongs to this specific object, and isn’t something it inherited. Here’s an example:

const car = { make: "toyota", model: "camry" };

// All JS objects inherit the .toString() method
// But it is not specific to our car object
car.hasOwnProperty("toString");
// false

Use the Javascript in operator

Javascript’s in operator is not that widely-used in my experience, but it’s a nice way to test if a key exists in an object. Importantly, the in operator will return true for inherited properties, unlike hasOwnProperty above.

const car = { make: "toyota", model: "camry" };

"make" in car; // true
"year" in car; // false

// "in" will find inherited properties from the prototype chain
"toString" in car; // true

// If you need to be *sure* it's local to this object, use hasOwnProperty
car.hasOwnProperty("toString"); // false

It is a bit unusual to see this syntax structure in Javascript, which is probably why in isn’t too widely-used, but if you’re coming from Python it feels downright natural.


Helpful Links

Notes

  1. Unlike golang, which will give you the zero-value for that value’s type ↩
  2. I say probably because someone could deliberately set a value as undefined, but that would be very unusual as it violates the principal of least surprise. To indicate blank data, you would typically see null instead of `undefined`. ↩

The post Check if key exists in Javascript object appeared first on Justin Joyce.

]]>
https://justinjoyce.dev/check-if-key-exists-in-javascript-object/feed/ 1
Replace a string in Python https://justinjoyce.dev/replace-a-string-in-python/ Sun, 29 Oct 2023 15:21:22 +0000 https://justinjoyce.dev/?p=1397 To replace a string or substring in Python you have two main options: Python’s String Replace Method The string.replace method works roughly how you would expect it to: By default, string.replace will replace all occurrences of the string you want to replace. However, it accepts an optional integer third argument specifying how many times to […]

The post Replace a string in Python appeared first on Justin Joyce.

]]>
To replace a string or substring in Python you have two main options:

  1. Built in string.replace()
  2. Python’s standard library re.sub()

Python’s String Replace Method

The string.replace method works roughly how you would expect it to:

my_string = "abc123"
new_string = my_string.replace("b","X")
print(new_string)
# "aXc123"

By default, string.replace will replace all occurrences of the string you want to replace. However, it accepts an optional integer third argument specifying how many times to replace:

my_string = "aaabbbccc"
my_string.replace("a", "X", 2)
# "XXabbbccc"

One important note: string.replace does not modify the existing string, it returns a new copy with the replacement performed.

Since string.replace returns a string, you can also chain replace operations:

# Chain as many replacements as you like
my_string = "aaabbbccc"
my_string.replace("a", "X", 2).replace("b", "").replace("c", "Z")
# "XXaZZZ"

However, with more complex replacements you might want to use regex instead.

Python Regex re.sub

Unless I’m doing a simple character swap or character removal (replace with empty string), I tend to use regex. If you can create the regex pattern, re.sub can probably use it to replace content for you.

Regex is a deep topic, and I actually wrote up a regex cheatsheet post, but here’s a simple re.sub example:

import re

my_string = "abc123"

# remove all digits
new_string = re.sub("\d", "", my_string)
print(new_string)
# "abc"

Like string.replace above, using re.sub does not modify the original string, it returns a new copy.

Here’s an example from my day job, comparing two XML files:

# Similar to an actual task I had to do at work recently
import re

# These two files were supposed to be the same, but the
# spacing and indentation made them hard to compare
with open("file_one.xml", "r") as file_one:
  xml_one = file_one.read()
  
# Don't forget your context managers!
with open("file_two.xml", "r") as file_two:
  xml_two = file_two.read()
  
tabs_or_newlines= "[\t\n]"

# substitute with empty string "" to remove tabs and newlines
xml_one_stripped = re.sub(tabs_or_newlines, "", xml_one)
xml_two_stripped = re.sub(tabs_or_newlines, "", xml_two)

# Without the weird tabs and line breaks, they should be the same
if xml_one_stripped == xml_two_stripped:
  print("they're the same")
else:
  print("not the same")

For more information, check out the helpful doc links below.


Helpful Links

The post Replace a string in Python appeared first on Justin Joyce.

]]>
Regex Cheatsheet https://justinjoyce.dev/regex-cheatsheet/ https://justinjoyce.dev/regex-cheatsheet/#comments Fri, 27 Oct 2023 20:30:22 +0000 https://justinjoyce.dev/?p=1365 Regex is very powerful and available in virtually every language. Eventually I’ll write a whole post about why every engineer should know some Regex, but this post is all about common regex patterns, flags, etc—in easy-to-read table form. First, here are some patterns similar to ones I’ve used in recent memory: Pattern Meaning Example Match […]

The post Regex Cheatsheet appeared first on Justin Joyce.

]]>
Regex is very powerful and available in virtually every language. Eventually I’ll write a whole post about why every engineer should know some Regex, but this post is all about common regex patterns, flags, etc—in easy-to-read table form.

First, here are some patterns similar to ones I’ve used in recent memory:

PatternMeaningExample Match
\d+match 1 or more digitsabc123def
\w+$match 1 or more word chars before end of lineabc-123def
ab|dematch ab or deabc123def
(?<=:)\w{4}match exactly 4 word characters preceded by a “:”abc:123def
(?<=\w+):match a : preceded by 1 or more word charactersabc: 123

Now to break down the common Regex elements.

Regex Character Classes

CharacterMeaning
\wany alphanumeric character, does not match accented characters1
\Wany non-alphanumeric character
\swhitespace (includes tabs, line breaks)
\Snot whitespace
\dany digit (same as doing [0-9])
\Dnot digits
.match any character (except line breaks)
[ab]match any character inside the [] (same as a|b)

Regex Anchors

AnchorMeaning
^the start of a line
$the end of a line
\ba word boundary
\Bnot a word boundary

Regex Quantifiers

QuantifierMeaning
+match 1 or more of the preceding token
*match 0 or more of the preceding token
{1, 2}match 1 or 2 of the preceding token
{2}match exactly 2 of the preceding token
{2,}match 2 or more of the preceding token

Grouping, Lookaheads, and Lookbehinds2

SyntaxMeaningExample Match
(abc)match the full group abcabc123
a|bmatch a or b, | is the regex “or”attribute
[abc]match any of a, b, or cattribute
[^abc]match not a, b, or ccarbs
[a-z]match any character in the range a-zaBc123
(?=abc)abc must come after your match: hi(?=abc)hiabc
(?!abc)abc cannot follow your match: hi(?!abc)hi_there
(?<=abc)abc must precede your match: (?<=abc)hiabchi
(?<!abc)abc cannot precede your match: (?<!abc)hi well_hi

There is a lot more to regex, but combinations of the above classes, anchors, quantifiers, and groupings will get you very far. I recommend Regexr for a quick regex test bed and reference page, I’ve used it countless times (including while writing this post).


Helpful Links

Notes

  1. \w catches ascii word characters. Accented characters, like é, are unicode. Accented characters will also be interpreted as word boundaries (\b), so beware! For a starting point on how to handle unicode characters, try the helpful link above. ↩
  2. This is one area where you might run into some language-specific limitations. Golang, for instance, doesn’t support lookbehind matches with non-deterministic lengths ↩

The post Regex Cheatsheet appeared first on Justin Joyce.

]]>
https://justinjoyce.dev/regex-cheatsheet/feed/ 1
Copy an object in Javascript https://justinjoyce.dev/copy-an-object-in-javascript/ Sat, 14 Oct 2023 16:01:17 +0000 http://mdh.tqp.mybluehost.me/?p=850 There are several ways to copy an object in Javascript, and they fall into two categories: shallow copies and deep copies. Before going deeper, it might be helpful to define the two: If you’re copying a simple Javascript object with top-level properties only, all copies will be deep copies, regardless of what method you use. […]

The post Copy an object in Javascript appeared first on Justin Joyce.

]]>
There are several ways to copy an object in Javascript, and they fall into two categories: shallow copies and deep copies. Before going deeper, it might be helpful to define the two:

  • Shallow copy: a copy of an object which shares references with the original. Modifying properties on either object might affect both.
  • Deep copy: a copy of an object which is entirely independent from the original. Modifying either object will not affect the other.

If you’re copying a simple Javascript object with top-level properties only, all copies will be deep copies, regardless of what method you use. If you have an object with nested properties, however, then you need to be more careful.

Let’s see some examples using a very common shallow-copying method, spread syntax:

// Simple, top-level properties only
const original = { name: "justin", age: 100 };
const copied = { ...original };

// These changes will not affect the original
copied.name = "frank";
copied.age = 50;

console.log(original);
// { name: "justin", age: 100 }

The two objects above are independent. Changing one has no effect on the other because those are all “top-level” properties; there are no nested values.

Here’s a more complicated example, where the objects are not independent:

// The "first" name property is now nested inside another object
const original = { name: { first: "justin" }, age: 100 };
const copied = { ...original };

// This change will affect both objects, this property is nested
copied.name.first = "frank";

// This change will NOT affect both, it isn't a nested property
copied.age = 50;

console.log(original);
// {name: {first: "frank"}, age: 100}

cosole.log(copied);
// {name: {first: "frank"}, age: 50}

In this example, changing a value on a nested property changed the value for both objects. That’s because when copying nested objects, javascript copies them by reference1, rather than creating entirely new objects.

Note: this holds true for Javascript Arrays also, since arrays are just objects. An array containing only top-level numbers or strings will always be deep copied. An array containing objects (or sub-arrays) will have each of those nested objects passed by reference unless you explicitly deep copy them.

Now that we have that explained, let’s move on to common copying methods.

Shallow Copies

Here are the common ways people shallow copy objects in JS:

Spread syntax

This is by far the most commonly-used method:

const original = { name: "justin", job: "dev" };
const copy = { ...original };

// You'll often see this with some properties overridden
const copyTwo = {...original, name: "joyce" };

// You can combine multiple objects
const copyThree = {
  ...original,
  ...another,
  ...somethingElse,
};

Object.assign

const original = { name: "justin", job: "dev" };
const copy = Object.assign({}, original)

// You override properties here too
const copyTwo = Object.assign({}, original, { name: "joyce" });

// You can combine multiple objects this way also
const copyTwo = Object.assign({}, original, another, somethingElse);

Array.concat

const one = [1];
const two = [{ two: 2 }];

// merged will be shallow
const merged = one.concat(two); // [ 1, { two: 2} ]

// this change will affect array 'two' also
merged[1].three = 3;

console.log(two); // [{ two: 2, three: 3 }]

The other common array methods also create shallow copies: Array.from() and Array.prototype.slice().

Deep Copies

The only surefire way to create a deep copy in javascript (without a library) is via the built-in JSON module.

const original = { name: { first: "justin", last: "joyce" }};
const copy = JSON.parse(JSON.stringify(original));

// 'copy' is now a completely independent object
// changing it will not affect the original
copy.name.first = "Tom";

console.log(original.name.first);
// "justin"

The JSON method requires that your original object is fully string-serializable, which is not always the case. Functions, for example, cannot be serialized via JSON, and won’t be copied this way:

const original = { func: () => console.log("hi"), name: "justin" };
const copy = JSON.parse(JSON.stringify(original));

// The 'func' param will be lost
console.log(copy);
// { name: "justin" }

In cases like this one, you might want to use a library function like lodash cloneDeep.


Helpful Links

  • For more on the limitations of deep-copying via JSON, head over to the Moz Docs.

Notes

  1. It’s essentially passing a pointer reference ↩

The post Copy an object in Javascript appeared first on Justin Joyce.

]]>
Undo a Git Commit https://justinjoyce.dev/undo-git-commit/ Wed, 06 Sep 2023 13:07:11 +0000 http://mdh.tqp.mybluehost.me/undo-git-commit/ To undo a git commit, you have a couple of options: git revert and git reset. You can also amend prior commits.

The post Undo a Git Commit appeared first on Justin Joyce.

]]>
Need to undo a git commit? You have a few options.

  1. git revert
  2. git reset
  3. git commit amend

Git revert

Git revert is the most straightforward way to undo some committed code. Git revert will create a new commit on your branch which does the exact opposite of the commit you want to revert. Let’s see an example:

screenshot of commit messages

The most recent commit was a mistake, let’s undo it with a git revert head:

git revert result
You can also target the commit hash, e.g. “git revert 322446d”

The revert command above added a new commit to my branch which does exactly the opposite of the commit it reverted. I accidentally deleted something, and git revert put it back. This is what the history looks like now:

result of git lg
this is my custom git lg alias

The full history of my mistake is still there for posterity, but If I compare the current state of the branch with HEAD~2 (the commit before the mistake), there are no differences:

git diff showing no differences
nothing to see here

Using git revert has the advantage of preserving the entire git history—it keeps the record of the bad commit and the revert commit in the git log. For that reason, I’ve used this many times when rolling back bad code on a production branch, where you really want to keep the full git history intact, and where you really don’t want to git push –force.

Git reset

The simplest version of git reset will un-stage some changes you have ready to be committed:

results of git reset
git reset un-staged the changes to app.py

If your changes have already been committed you can reset to an earlier commit, and git reset has a few options specifying how to handle the changing code.

Git reset –hard

Git reset –hard is destructive, anything in the way of the reset will be deleted. If you reset –hard back 4 commits, the code from the most recent 3 commits will be lost.

That said, it’s very handy if you need to scrap your most recent commit (or commits). If you know for sure your most recent commit is not worth saving, just git reset --hard HEAD~1. In English, that command says “reset the HEAD of this branch back one commit”1. Here’s an example:

three lines of git log output

In the screenshot above, I accidentally committed something directly to the main branch, which is generally not a good idea. Let’s assume we can just delete that commit—we don’t care what’s in it. That’s where git reset --hard comes in:

a screenshot of git reset --hard

My local HEAD has now been moved back one commit, and if I run git log again the first commit above–”did something awesome”–is gone:

A note on HEAD~1

Using HEAD~12 is not commonly seen in git commands other than this one, but it is available; most git command examples use a specific commit hash instead.

Head~1 literally means “one commit before HEAD”. If you wanted to reset back two commits, you would reset --hard HEAD~2, and so on. You can also git reset to a specific commit hash—the example above could equally have been written using git reset --hard b1385919.

Git reset –soft

This is a more gentle version of git reset. Running git reset --soft HEAD~1 will roll your branch back one commit, but instead of deleting the most recent code it will put it back in your “changes to be committed”, and let you modify the commit as you normally would. If you git reset --soft back several commits, all of the intermediate commits will be added to your staged files:

git reset soft example
The code from the most recent 4 commits is now staged

Force Pushing

With any of the above git reset options, you may have to git push --force to get your code to push to the remote. Git reset can change the history of your branch, and if the local git history does not match the repository’s remote history, github will raise error: failed to push some refs. Assuming your code is the latest, you can get around this error with a git push --force-with-lease3.

Git reset –keep

I have never actually used this option, but it allows you to run a git reset and keep any existing uncommitted changes you might have. In practice, I prefer the following:

  1. git stash
  2. git reset
  3. git stash pop

I find the three-step workflow above more clear than using –keep.

Git commit –amend

This one doesn’t exactly undo a commit, but it does allow you to modify the most recent commit. I have a whole post about this command, but here are a few quick examples:

# Modify the message of your most recent commit
git commit --amend -m "new message for commit"

# Add a file to your most recent commit without changing the message
git add forgotten_file.txt
git commit --amend --no-edit

With either of these versions of git commit amend you will have to force push, or better yet: git push --force-with-lease. For an explanation of why, go to this section of my post about amend.


Helpful Links

Notes

  1. That’s not exactly what it means, but HEAD is a complicated git concept. For a great explanation, see this StackOverflow answer. ↩
  2. Another plug for the link about HEAD in note 1 above ^ ↩
  3. I have more detail about force pushing in this post about amending commits ↩

The post Undo a Git Commit appeared first on Justin Joyce.

]]>