BOXES v0.13

I hate magic numbers. I love The Magic Numbers but I hate how many numbers are just there in our code. Let's look at some of it:

59# We add a "separation" constant so you can see the boxes individually
60separation = .05
213    pages = [Box(i * 35, 0, 30, 50) for i in range(10)]
221    draw_boxes(text_boxes, pages, output, ("100cm", "50cm"), True)

Magic numbers are what's called a code smell. When you read the code, you should be wrinkling your nose, like when you get sushi and it smells just a little bit off. Other code smells include global variables, and separation is actually both things, a global magic number!

We are deciding that 0.05 is a reasonable separation between letters. We are deciding there are 10 pages , they are 30x50 and next to each other with a specific separation.

Magic numbers are bad because they encode a choice. They are literally us choosing something and making it part of the code. By being inside the code, they are outside the reach of the user. In some cases the magic numbers have a good reason for existing. In others they should just be a default for something the user can change.

There are additional decisions not in numbers, such as using the Arial font, but we will get around to them later.

So, let's get rid of the stench, and make those things part of the interface we provide the user.

Let's go in order.

For separation we can make an optional argument defaulting to that value. The pages list, we could take a page size argument at least. And for the drawing size, we will just make the drawing big enough to show all the boxes.

A bit trickier is the number of pages, which was hardcoded to 10. We will just create a large number of pages and remove the unused ones.

The first two are just a matter of adding them to the docopt data. The parts between [] brackets are optional.

1"""
2Usage:
3    boxes <input> <output> [--page-size=<WxH>] [--separation=<sep>]
4    boxes --version
5"""

Then getting the values out of the arguments (or use defaults), and pass them along to our existing functions:

230if __name__ == "__main__":
231    arguments = docopt(__doc__, version="Boxes 0.13")
232
233    if arguments["--page-size"]:
234        p_size = [int(x) for x in arguments["--page-size"].split("x")]
235    else:
236        p_size = (30, 50)
237
238    if arguments["--separation"]:
239        separation = float(arguments["--separation"])
240    else:
241        separation = 0.05
242
243    convert(
244        input=arguments["<input>"],
245        output=arguments["<output>"],
246        page_size=p_size,
247        separation=separation,
248    )

Adding the arguments to convert and passing them along where they make sense. Additionally, we calculate the drawing size instead of just hardcoding it.

217def convert(input, output, page_size=(30, 50), separation=0.05):
218    pages = create_pages(page_size)
219    text_boxes = create_text_boxes(input)
220    layout(text_boxes, pages, separation)
221    draw_boxes(
222        text_boxes,
223        pages,
224        output,
225        (f"{pages[-1].w + pages[-1].x}cm", f"{pages[-1].h}cm"),
226        True,
227    )

And making the obvious changes in each function. In create_pages we use the page size:

210def create_pages(page_size):
211    # A few pages all the same size
212    w, h = page_size
213    pages = [Box(i * (w + 5), 0, w, h) for i in range(1000)]
214    return pages

In layout we pass separation as argument, which requires no changes, and add a little code to delete the unused pages:

159    # Remove leftover boxes
160    del (pages[page + 1:])

I suggest you take a close look at the diff for this lesson. to see all these small changes in context.

What have we gained for this effort? Well, we can now do things that used to require editing code. Such as using very small pages:

python boxes.py pride-and-prejudice.txt lesson2.svg --page-size=10x20

And that, unfortunately, exposes a whole lot of bugs.

lesson2.svg

We will get around to them. Don't worry. We just need to do a little more groundwork.


Further references:

results matching ""

    No results matching ""