Aug. 16, 2024, 1:14 p.m.

Learning Things From Search or AI Results

Sustainable Development and More

SocialMedia.jpg

Before Stack Overflow, programmers at the time had to learn things by knowing someone who had the info, reading documentation, or going through code (if it was available). Stack Overflow, Google, and now ChatGPT provide more direct solutions to our problem, allowing us to get unstuck and move forward more quickly than before.

But, you end up not knowing if the solution is actually the best way to solve the problem, and usually don't
have any real insight into why the presented solution works. Practically speaking, Stack Overflow and AI
chatbots are typically out of date and can give answers that were correct, but now aren't.

Here's how I deal with this problem without abandoning these valuable tools.

Example

Let's say you want to install NodeJS inside a Docker image you are building.

Kagi's top result for "install nodejs inside docker image" is this Stack Overflow question from 2016. The top
answer has you installing a node version manager to then install Node, while the answer marked as correct
instructs you to build from source.

Kagi has a built-in AI chatbot powered by Claude that gives a "quick answer" to any search (only if you want). It's answer is extremely verbose and over-explains, but it does contain some pretty specific instructions. They are to use curl to download a script from deb.nodesource.com, execute that script, and then use apt-get to install nodejs.

Both Stack Overflow answers are completely wrong, at least as of 2024. They may work, but would likely be
pretty painful to try and debug, and a nightmare to maintain.

Claude's answer is much simpler and definitely works, even though most of the text that explains what it is doing is misleading or wrong. But, it will work.

Following Claude's advice, however, doesn't really teach you much. If you need to make a change later, or
debug a problem in the future, you basically have to start over again. You also can't really be sure that
what Claude told you to do is the best way to do it.

Here is how to become sure.

Work Backwards from the Solution

When I can, I try to work backwards from a working solution until I find documentation from the software
vendor that explains what to do.

Depending on how much you understand what you are doing, this could be easy or could be involved. For
example, in the example above, you need to eventually figure out:

  • What's happening inside a Dockerfile
  • What do FROM and RUN do
  • How does apt-get work and why is the shell script from nodesource.com needed?
  • What is the relationship between NodeJS and nodesource.com?

Let's say you understand the Docker stuff: Dockerfile is building an image, FROM declares your operating system and version, RUN executes code, and apt-get installs software.

If you didn't have Claude's answer, starting from nodejs.org and trying to find out how to do this would be pretty difficult. But, with some sort of working answer, you can navigate nodejs.org more
intentionally.

Clicking “Download” leads to a nice page where you can configure your OS and installation method.
Unfortunately, the “Docker” choice isn't what we want. You have to know how Docker works to know that the
answer isn't correct, but you can also see that it's not giving you directives that go in Dockerfile. But, there's more on that page to explore.

There's a link to “Check out other community supported package managers”. From there, you can click "Debian and Ubuntu based Linux distributions" (since you know that FROM ubuntu means Ubuntu), and end up on a GitHub page that has instructions.

And these instructions are pretty close to what Claude told you. They have some minor differences, and
install a later version of Node. But it does validate that Claude was mostly correct. More importantly, however, you now have a definitive source for the right way to do this.

Even though Claude wasn't 100% correct, it led you to the right answer, and it may have been extremely difficult to find that answer without some sort of help as to where to begin.

Try It Yourself

Here's an exercise you can do yourself. I'm assuming you do not know how to parse command line arguments in Bash, so if you want a script that accepts -v and -s «service», this is how you'd do it:

SERVICE=default-service
VERBOSE=0
while getopts ":vs:" opt "${@}"; do
  case ${opt} in
    s)
      SERVICE="${OPTARG}"
      ;;
    v)
      VERBOSE=1
      ;;
    ?)
      echo "Invalid option: ${OPTARG}"
      exit 1
      ;;
  esac
done
shift $((OPTIND -1))

Try to figure out why this works and see if you can piece together documentation from Bash and getopts
to convince yourself this is the right way to do this.

A Generalized Set of Steps

Here is what I do, in a general sense:

  1. Search for the solution to a problem, finding at least two answers.
  2. Check them and go with the one that seems best.
  3. Consult the documentation for the technologies in question and locate explicit documentation that explains the solution you chose in step 2.
  4. If needed, update your code to more closely match what the software vendor advises.
  5. Reference the vendor's documentation in your code, if needed.

Sometimes, if I have time, I'll do a sixth step, which is to just…read more of the documentation. I'll explore it and see what other capabilities are sitting there, waiting for me to use. It can be quite inspirational!

TIL

Here's some esoteric Ruby for you: name-based dependency injection based on method parameter names!

Let's say you have some objects in some sort of context:

context = {
  payment_service: PaymentService.new,
  tax_calculator: TaxCalculator.new,
  shipping_determination: ShippingDetermination.new,
}

Now, you want to make a class that needs a PaymentService and a TaxCalculator:

class Checkout
  def initialize(payment_service:, tax_calculator:)
    # ...
  end
end

Ruby allows you to access the names of the parameters:

> Checkout.instance_method(:initialize).parameters
=> [[:keyreq, :payment_service], [:keyreq, :tax_calculator]]

You can use those names to grab instances from your context, creating a hash of arguments like so:

args = Checkout.
         instance_method(:instance_method).
         paramters.map { |(_param_type,param_name)|
           [ param_name, context.fetch(param_name) ]
         }.to_h

With args as a Hash, you can now create a Checkout instance by converting it to keyword args using ** and passing it to new (which calls initialize):

checkout = Checkout.new(**args)

You don't always—or often—need dependency injection, but if you do, you could make a basic container for it in, well, not very many lines of Ruby.


Unless otherwise noted, my emails were written entirely by me without any assistance from a generative AI.

You just read issue #17 of Sustainable Development and More. You can also browse the full archives of this newsletter.

Share on LinkedIn Share on Hacker News Share on Reddit Share via email
Website
Powered by Buttondown, the easiest way to start and grow your newsletter.