The variables are pointless. Your lib and include directories are already defined somewhere else: your project's directory structure.

On the compiler command line, you just want -iquote include; -iquote $(INC_DIR) doesn't help anything.

If someone renames or moves directories in the future, they can search and replace the Makefile.

Variables hide things. If I see $(INC_DIR), is that something external? I have to look it up.

By default I assume that a variable exists for a reason, which is that something is being made configurable. Usually, configurable things are uncertainties that are external: where other stuff is, and what not.

The article further recommends nonstandard practices.

- LFLAGS: what is that? The standard variables are LDFLAGS and LDLIBS. LDFLAGS are options for linking, and LDLIBS are just the libraries like -lwhatever. They are separated because they go into different parts of the command line. For Pete's sake, do not invent your own variables. Learn the standards. Distro maintainers will thank you when your program is packaged.

- Believe it it or not, but you must should not touch CFLAGS. CFLAGS belongs to the user who is invoking your Makefile. Put any required options or debug options and whatnot into a different variable. Combine that with CFLAGS. The same goes for the aforementioned LDFLAGS and LDLIBS.

Let's look at this:

ifeq ($(debug), 1)
CFLAGS := $(CFLAGS) -g -O0
else
CFLAGS := $(CFLAGS) -Oz
endif

This does not always do what the author thinks. It looks like it wants to add some options to CFLAGS, if CFLAGS is already defined. But this is not what GNU Make will do if CFLAGS is coming from the command line:

make CFLAGS=-O2

then the := assignment will be entirely suppressed. That -O2 will be the CFLAGS. The combination will happen if CFLAGS is coming from the environment.

Thus if you have essential code generation options that your program needs, the above pattern will break in some situations. Don't use CFLAGS as the container where you accumulate all your options. Assemble the options in your own variable, where you interpolate CFLAGS:

OUR_CFLAGS := $(CFLAGS) $(OUR_DIALECT_FLAGS) $(OUR_CODE_GEN_CFLAGS) $(OUR_DEBUG_FLAGS) ...

OUR_LDFLAGS := $(LDFLAGS) -Llibdir

OUR_LDLIBS := $(LDLIBS) -lutils

I see the article is using a double star operator in a $(wildcard ...) call. I don't see that documented in GNU Make, and it's not obvious from looking at all the glob-related code that any such thing is implemented.

The following has a subtle problem:

# Build object files and third-party libraries
$(OBJS): dir
@mkdir -p $(BUILD_DIR)/$(@D)
@$(CC) $(CFLAGS) -o $(BUILD_DIR)/$@ -c $*.c

The problem is that a directory timestamp changes often. This rule will cause the $(OBJS) to be considered out of date whenever the timestamp of dir is touched.

For this you need to use the GNU Make "order-only prerequisite" mechanism, where order-only prerequisites are separated by a bar:

# Build object files and third-party libraries
$(OBJS): | dir
@mkdir -p $(BUILD_DIR)/$(@D)
@$(CC) $(CFLAGS) -o $(BUILD_DIR)/$@ -c $*.c

The GNU Make manual has exactly this kind of example in the section on order-only prerequisites.

$(NAME): format lint dir $(OBJS)

This is running a format step that touches all the sources in the link step of the program. This is just silly and far removed from the purported goal of crafting a clean Makefile. Not only shouldn't build steps be touching the sources, but the dependencies this cruft brings in are unpalatable.