When I went to grad school, my career
had been programming, and I'd done a lot
of it for some years. Two applied math
profs were starting a computer science
course and asked me what good lessons I'd
learned, and I answered. The answer
remains current:

A programmer stands between a real problem
and a computer.

The computer does only
what operations it is carefully instructed
to do and no more understands the real problem
than the chef's knife of a good cook
understands cooking.

So, the work of the programmer is to take the real
problem and see how to use the computer to
solve it.

For this work, as a first step,
if only to make the work easier
for the programmer, get a first list of the
data inputs for the problem and the data
outputs. Yes the code for a Web page is
a special case of this first step.

Then outline how the program will take the
inputs and generate the outputs.

Here likely will need to define storage
for the inputs, say, some or all of
variables, arrays,
data structures, instances of object-oriented classes,
database tables, etc.

Then move on to the work manipulating
the input data to get the desired output
data. In the case of a Web page or any
software with a graphical user interface
(GUI), also will want to outline what the
output will look like on the user's screen.

Then for the manipulations of the data,
use the old strategy of divide and
conquer, that is, break the work down
into largely or entirely independent
pieces of work. People have been doing
work of wide variety this way for
centuries, likely back to the pyramids
and the first boats that could
cross an ocean.
For each such piece,
have in the software a section of code,
subroutine, or function. The work of such a piece
may be further subdivided.

In the end, want each such piece of
code, section, subroutine, ...,
to be small, to have a purpose that is
easy to explain briefly, that is
fairly easy to debug, by desk-checking
(that is, careful critical reading),
running test cases, etc., and with logic
that is fairly easy to create, explain,
and follow.

This division of the work should
be, at least intuitively, robust
to small changes in the real world
problem to be solved. That is,
the division should be for a problem
a little more general and complicated
than the one actually given; how much
more is a matter of judgment, but
a small change in the problem statement
should still be a small change
in the software!

Next, in the division, exploit
the scope of names rules in the
programming language to help
the software pieces be independent.

Next, remember that at least one
human needs to be able to understand
the source code and that
"When a program is written, it is
understood only by the programmer
and God. Six months later, only God."
So, humans need to be able to understand
the code, for small projects, just by
reading the source code and not
needing additional documentation
outside the source code.

Next, remember that humans communicate
in natural languages, e.g., English,
hopefully with sentences, paragraphs,
etc. Remember that, no matter how much
we might wish and intend otherwise,
the source code of software is not
in a natural language. Yes we can
use mnemonic spelling for the various
names we choose and use, and such
mnemonic naming does help someone trying
to read and understand the code, but
such mnemonic names are still a long
way from English. Bluntly, as
information for a human reader, the
code doesn't really mean anything or
is likely a puzzle problem to solve
to guess the meaning.

So, net, have to document the code,
that is, explain the code to
a human, explain with English,
essentially with sentences,
paragraphs, etc.

Here is an example of a technique
in source code documentation:
My project now is a new Web site;
the source code for the Web pages is
in Microsoft's Visual Basic .NET
making use of the .NET Framework
(collection of object-oriented class),
SQL Server,
ASP.NET, and IIS. For this work, I have
5000+ Web pages of documentation, nearly
all from Microsoft's Web site MSDN.

So, my code makes use of a lot of
classes, functions, etc. from
those 5000+ pages of documentation,
and some of those classes, etc. do
a lot and need some good documentation.
So, in my source code, when I use
one of those classes, functions, etc.,
I insert in my source code a comment
with the title of an appropriate
Web page of documentation along with
a tree name of the Web page on
a hard disk on my computer. So, when
reading such source code, one keystroke
in my favorite text editor
(which is what I use to write code)
will display the Web page so that
I can confirm that my source code
is doing what I intend.

Since the
code is awash in symbols,
good examples of how to
explain code are in good texts
in subjects based heavily on
symbols. So, can explain code
much like a good freshman physics
text explains Newton's second
law or Coulomb's law or how
a good freshman calculus text
explains conic sections or
differentiation. E.g., for
English readers, mathematics is
still written in English;
then the symbols are names,
that is nouns.

In large projects, the
documentation may be
hundreds of pages.
But essentially always there
is documentation as source
code comments in the source code.

Then, six months later God, the
programmer, and others will all
be able to understand the code!

My view is that currently the biggest
bottleneck in practical computing
is poor documentation. Sorry 'bout
that. YMMV!