I have not compiled a C file in anger for years, and yet nearly every repository I touch grows a Makefile. Not for building objects. For remembering things.
The problem it solves is dull and universal: every project accumulates a dozen incantations. The right flags for the test runner, the env vars the linter wants, the exact docker run line with all seven mounts. They end up scattered across a README, a half-remembered shell history, and someone's private notes. A Makefile is just a phone book for those commands. make test, make lint, make fmt, make up. You stop thinking and start typing.
.PHONY: test lint fmt
test:
go test ./... -race
lint:
golangci-lint run
fmt:
gofmt -w .
People reach for just or a Taskfile now, and they are genuinely nicer: no tab-versus-space landmines, real argument handling. I use just when I get the choice. But make is already on every machine I will ever SSH into, and a new colleague can read the above and know exactly what runs without learning a new tool. That portability is worth a surprising amount.
The trap is letting it grow real build logic. The moment a target has an if and a loop and starts caring about file timestamps, write a script and call the script. The Makefile should stay the index, not the book.