Beyond PEP 8 – Best practices for beautiful intelligible code [video]
youtube.com
youtube.com
Whenever I try to think "How would someone smarter than me solve this?", Raymond is one of the people I am thinking about. I'd highly recommend this talk.
Aside: A few days after seeing this, a co-worker asked for my recommendations on a Python project he was working on. Which involved some Python code that was translated over from Java. Pretty much exactly what Raymond was talking about fixing here. I told my coworker he should watch it. A week later he comes to me following up on it, I ask if he's watched it. No... Then he proceeds to ask me what sort of indentation style he should use and what format for the inline docs.
Some takeaways; 1. Adhere to a great style guide 2. Can you predict what the code does? Can you trace a path through the function calls, if statements and for loops? 3. Can you explain to someone what the code -means-? Why is it the way it is?
p = (170, .1, .6);
if p[1] > .5:
print("bright!")
elif p[2] > .5:
print("light")
That's ok, right? Pretty nice. I can see that it'll print "light".Good enough? No, not really. How are "bright" and "light" related to (170, .1, .6) and .5?
How about this?
color = Color(hue=170, saturation=.1, luminosity=.6)
if color.saturation > .5:
print("bright!")
elif color.luminosity > .5:
print("light!")
Far better! I know now that saturation is related to brightness, and that luminosity is related to lightness.
It takes about the same time to write, yet is far more legible.Those things are better explained in a comment, I'd think.
Comments are needed, but should not explain what the code does (as in "add 1 to n") and instead exaplin why it does it (as in "fn expects indices to start from 1").
It can and the code in question will print both "bright" and "light" if passed a color that matches that.
Where did you see that it's an either-or affair in the example?
Besides, the key point, is that the code talks about luminocity and brightness levels, and the second example makes that explicit. No comments needed if you know what HSV is. And if you don't, comments are usually not the place to learn about it.
if color.saturation > .5:
print("bright!")
elif color.luminosity > .5:
print("light!")
it's an `elif`... it will never print both "bright" and "light"This kind of mistake really detracts from the point that was argued here about code being "intelligible"
I didn't read it carefully here, as I had already seen the presentation and it wasn't until your disagreement I realized the code was different.
There's nothing wrong with if/elses, but I strongly prefer pattern matching, where available. And even more, I think it's important to keep computation and presentation separate... that is, to not entagle IO with the rest of the code.
(Unfortunately Haskell is the only language I know of that gives you a tool to tackle that)
Obviously this was just a small example, but I've seen unintended IO byte back the developer in real world (Python) code.
And another small comment: since I haven't bothered to comment on Hettinger's talk before now.
I liked it (but played it back at 1.5x)... but since it's not first talk by Raymond Hettinger I saw, I knew that his delivery is good. Some of the stuff (should?) be obvious to every (Python) developer (but repetita juvant). I didn't like the jab at Java for the mistake of iterating over indexes: there's no excuse to writing that code not even there (look at the Iterator and Iterable interfaces)
$ python
>>> if True:
... print "First"
... elif True:
... print "Second"
...
First
>>>That's not the code in his example. Here's that code:
Yeah, didn't pay close attention to it, thought it was the same as the one I saw in the presentation.
def qualify(color):
hue, saturation, luminosity = color
if hue ... or saturation:
...
...For some reason youtube didn't let me change the speed of this video. Instead I downloaded it with youtube-dl and watched it with vlc.
As an example, check out the 7th example in http://research.microsoft.com/en-us/um/people/smcdirm/apx/in....
A humbling reminder of how far I have to go to be even near this guy's level.
runWithNetworkElement("171.0.2.45", networkElement -> {
for (Route route : networkElement.routingTable()) {
System.out.printf("%15s -> %s%n", route.name(), route.ipaddr());
}
});Sure, Java 8 has lambads, but have their disposed of getters and setters and added some kind of properties?
If not, then the idiomatic way is not name(), it's getName().
(Of course without "unified property access", it's not that much different)
For example, both Google's AutoValue library and the Immutables library show examples of getters that drop the "get" prefix:
https://github.com/google/auto/tree/master/value
One argument against the prefix is that immutable value types don't have setters, so it's not as important to distinguish between them. Another argument is that immutable values aren't really objects in the object-oriented sense, so accessing a field really is just a data access as oppose to an action being performed by the class instance.
(I guess one aspect of Java that makes this less awkward to implement is that methods and fields have independent namespaces, so nothing stops the Route class from having both a "name" field and a "name()" method. That's possible because Java doesn't have first-class functions, so you can always determine from usage whether something is a field or a method.)
That being said, I almost couldn't get past the obnoxious rant at the beginning against maximum line length. The reasons for the rule are made very clear in pep8 as well as the fact that it's not a hard rule and it's fine to override it in your team if you all agree on longer lines.
And are still BS with today's monitors (including laptop monitors).
Besides, PEP8 itself goes on to say: " For code maintained exclusively or primarily by a team that can reach agreement on this issue, it is okay to increase the nominal line length from 80 to 100 characters (effectively increasing the maximum length to 99 characters), provided that comments and docstrings are still wrapped at 72 characters."
My 15" laptop monitor can only (readably to me) fit two 100-wide buffers in a visually barebone editor (emacs), move to an editor or IDE where the UI takes more horizontal space because of sidebars (Atom, IntelliJ) and the buffers are close to 80-wide, so 80-wide is a perfectly fine limit as far as I'm concerned.
Until I have to perform a 3-way merge, then 80-wide doesn't fit anymore.
And age will eventually catch up to me and make things worse.
And for those, you can always either wrap or scroll a little to see them in a 3 way merge, it's not a big deal.
Heck, in the eighties that you champion and that they got that rule, they worked with a single 80-wide terminal view, not 2 100-wide side by side and no 3 way merges.
I doubt that I was or am nearly as good an editor as he is a coder. Yet I think that there is a logical progression (not a natural progression, since we don't all make it) from knowing the rules to knowing what the rules are for, to having the confidence to break them when it makes sense.
A few months ago I started to get into some networking and saw that there were a few free online courses available. Unfortunately, despite choosing my favorite one, I ended up falling asleep 3 times already listening to the lectures. Man, I sound like I'm getting old...
And then I realized this is all BS. I wrote a very short styleguide with a linkback to pep8 for any "questions" one might have. Problem solved. You don't need to decide every little detail of how your code is going to look in advance, unless there is a solid reason to do so.
The styleguide, for those curious: https://github.com/jleclanche/fireplace/blob/master/CONTRIBU...
Edit: What a great talk. Really embraces the way I've been thinking about pythonic coding. Loved it.
Sure there are huge monitors with insane dpi allowing a ridiculous amount of text, but I'm not getting younger and find myself increasing font size every now and then to be able to read.
Getting old sucks sometimes.
If you take a blatantly non-compliant codebase and PEP8-ify it, you'll have many "pep8" lines in your git blame for a point, but by integrating it in your process that will disappear pretty clean.
In fact, he proposed the total inverse of what you say.
He said, code standards like PEP8 are good, and should be followed. But those are only code formatting standards.
The beyond part wasn't about doing things "his way instead of the standard way" but about doing things the Python way (idiomatic Python) instead of ad-hoc or mimicking other languages you're familiar with way.
What he showed are common standard Python approaches to solving issues, taking advantage of what Python offers instead of writing in C or Java etc style.
Really, your comment couldn't be further from the spirit and content of the talk.
(Of course this is already obvious since the 80-column thing is about PEP8, whereas the essense of the talk is how to better structure your code BEYOND PEP8's formatting rules).
Not to mention that even PEP8 concedes that you can raise the max line length limit up to 100 if your team agrees.
I know some people program with one editor maximised and can have 200 character lines if they like, but a specific width as a convention is helpful. And the convention that is already widely used is going to make fewer people have to accommodate you. The 'ish' is useless in that regard.
I like standards, but 80 chars just isnt enough and never was. Is 96 the next reasonable number?
Most lines in code that is well structured and simple are less than 80 cols wide. Lines that require more than that can be broken. I think in 20 years of Python I've only seen a couple of cases where there wasn't an obvious semantic break point, most of the time such breaks are easy to place at logical points.
When you have 80 cols you have more code in your window. You can use the resolution to have taller windows and more windows side-by-side. 1920 pixels gives you nicely 3x80 col editors. Which allows you to look at the code, and its tests, and the shell at the same time, for example.
Depending on your font. I use 1920 (-Misc-Fixed-Medium-R-SemiCondensed--13-120-75-75-C-60-ISO10646-1), and I get almost 4x80 col's, 3x96 works nice.
As the commenter above stated, he found considerably less than 1% of his lines required > 80 cols, so rather than go 96 cols and have 17% of my screen real estate wasted, I'll have the extra column of code or documentation or tests, or output, and have to toggle less.
I’ve seen 66 characters recommended as a maximum line width as a style guide for typewritten documents. You also want some extra room for extra margins, line numbering, etc. If 66 is taken as a good minimal number, then 64 (2⁶) is too small, so let’s add, say, 16 to 64, giving 80 columns.
Paper documents have the form that they do for a reason. You never saw someone writing sideways on a paper (landscape alignment). This is because readability suffers when lines are too long. Even if boxes resized automatically, it’s the line length that’s the problem, not the resizing or scrolling.
You can't standardise based on what you are fond of. That's the point of a standard. When such a large proportion of people, and the standard library, and most shells, use 80, then using 120 will inconvenience them. If you load my 80col code into your 120 col editor, it will not inconvenience you at all, barring an occasional extra line break.
>>py34 lns.py
total lines: 2099
0.063% over 80
Not many apparently, and a significant portion of them are hard coded URLs/strings in scripts. I guess I stand corrected, 120 isn't that useful... import os
from pathlib import Path
def walklines(glob='**/*.py'):
for filename in Path(os.getcwd()).glob(glob):
with open(str(filename)) as file:
for line in file.readlines():
yield line
if __name__ == '__main__':
alllines = [line for line in walklines()
if line]
totallines = len(alllines)
cutoff = 80
total_long_lines = len([line for line in alllines
if len(line) > cutoff])
prcnt_long = total_long_lines / totallines
print('total lines: {}'.format(totallines))
print('{:.3f}% over {}'.format(prcnt_long, cutoff)) variable = some_function((some nested expression that is very long
and needs to wrap))
as variable = some_function((
some nested expression that is very long but may not need to wrap
but if it does there's tons more room for it))
I.e. you don't have to differentiate between 'syntax indents' and 'expression indents', every thing is just an indent of the same size. Which has the advantage that almost every editor can indent it correctly, and it works with both space and tab indents, whereas you're at the mercy of your Python editing mode whether the 'indent to sibling' works for everyone (and if not, the programmer has to sit tapping space to line it up right). Both approaches are in PEP8, but i find that simple change removes 99% of all >80 line cases for me.The only caveat, also covered in PEP 8 is for function definitions.
def some_descriptive_function_name(one_parameter,
another_parameter):
some_functionality()
becomes def some_descriptive_function_name(
one_parameter, another_parameter):
some_functionality()
i.e. the parameter lines are double indented. def some_descriptive_function_name(
one_parameter,
another_parameter,
):
some_functionality()
Anyway, either one is superior to 'indent to sibling', where the lines are now tightly coupled to each other.But, coding standards aren't about tastes, they are about coordinating large numbers of programmers to write code in consistent ways. PEP 8 gives the 'double indentation' as standard, rather than the 'K&R' style.
You're free to write however you want to, and if you're the tech director of a company, you can tell everyone else to follow your aesthetics. But you'll be doing a lot of work with code written according to PEP8. In my experience, learning and disciplining your team to use the standard is better than having a house-style. But YMMV, of course.
PEP8 isn't my 'ideal' aesthetic for Python either, but I'd need a far better reason than 'I think that looks ugly' for not using it.
I also agree 'indent to sibling' is poor, and not very universally tool-supported, even though it is part of the PEP8 style.
No.
>You can't standardise based on what you are fond of.
Sure you can. That's why company and team-wide standards are for.
Nobody has to be forced to use some BS 70's throwback rules just because they are in PEP8.
When lines are longer it becomes more difficult for your eyes to keep track of where the next line is. Furthermore, code is not formatted as paragraphs but as separate lines, so you end up wasting a lot of screen estate for those few long lines. Having a low maximum means it will be close to the average and a larger proportion of the screen is utilized.
Actually this claim is disputed: http://psychology.wichita.edu/surl/usabilitynews/72/LineLeng...
(Go, for some reason, doesn't like splitting lines at all, no matter how wide. I've had code reviewers criticise me splitting lines at 200, which I don't understand at all.)
if __name__ == '__main__':
def this_is_a_function(*args, **kwargs):
def this_is_a_helper_function(arg1,
arg2=('this line is already indented 39 '
'chars!')):
this_long_variable = ("Don't put an expression here unless you "
"want to use useless intermediate variabls.")
return this_long variable if var else 'really short here'
return 1
And some people want to insist on 8 char indents! if __name__ == '__main__':
def this_is_a_function(*args, **kwargs):
def this_is_a_helper_function(
arg1,
arg2='this line is now indented 12 chars!'
):
this_long_variable = (
"Don't put an expression here unless you "
"want to use useless intermediate variabls."
)
return this_long variable if var else 'really short here'
return 1 def this_is_a_function(*args, **kws):
def this_is_a_helper_function(
arg1,
arg2='this line is indented 12 chars!'):
this_long_variable = (
'Don\'t put an expression here unless you '
'want to use useless intermediate variables')
return this_long_variable if var else 'really short here'
return 1
(for the same number of lines) or better still follow some basic common sense:1. Local functions should be short and have short names since their scope is limited.
2. Local variables, similarly
3. Don't put long explicit strings in code.
and get
messages = dict(
long_default = \
'Don\'t write strings that may wrap in ugly ways on 80 col shells.',
short_default = 'Keep it short'
)
def this_is_a_function(parameter):
def message(long, message='this is only 36 chars'):
long_message = messages.long_default
short_message = message or messages.short_default
return long_message if long else short_message
# Do something here.
return 1
And if you have an expression that is more than 80 characters long, breaking bits of that expression and assigning them to temporary variables is not 'useless', it makes code clearer and is only rarely a performance hit.In other words, I try to use vertical space for higher-level work than horizontal space, and when you have to fit both into vertical space, you lose some clarity in high-level in order to gain it in low-level.
But I think these things do have to be addressed with examples. There are few people willing to put an actual example up that a) is frequent enough to mean that 80cols can't be easily used (note, not 'preferred', we're talking about a standard, plenty of people 'prefer' 80 cols) and b) is pythonic enough that it isn't better written in more explicit steps.
So I don't think 80 cols is a hard limit, anymore than I think writing code in a terminal is a hard limit. I use a non-terminal text editor. I do it because it makes it easier to read code and format it how I like. I see little reason why anyone should be jumping through hoops to accommodate that. By using a terminal editor, you've already decided you won't get the best formatting, because you sacrifice the flexibility that allows it in exchange for something else. You should be expecting things to wrap around awkwardly on occasion.
Eighty columns is a good target width, but it's not worth proliferating clunky constructs to conform to. That's no better than having code wrap. (Of course, someone could also write terminal editors that allow text to go longer than 80 lines. Why are you using an editor like that if it can't do something this simple?)
I think we're agreeing mostly.
The problem with 'ish' is that I see it as worse than useless. 80cols-ish is what? 90 cols max? Why not just use 90 cols as the standard? If I have to scroll my whole window or resize it to read your line, is that really better than if it had an extra break? Some people may think so, but I don't, personally.
Flat is better than nested.
Too many levels of nesting is definitely a code smell. Often it's better to pull those helper functions out, and/or create function or method factory functions at a higher level of organization.Anywho, do you have a genuine example of where a closure is helpful for you in python? I'm very curious.
Ayup. That reason is teletypewriters (aka ttys).
The biggest problem I have with most programmers is naming things well. 80 columns is anathema to good naming. People start using mbl instead of maxExchangeBufferLengthBytes.
No, I'm not a Java guy. I spend a LOT of time chewing through embedded C code. I don't even have good autocomplete in most embedded IDE's, and I STILL prefer maxExchangeBufferLengthBytes. Here's why--that name tells me many things at a glance:
max--limit of some form, probably should be positive
ExchangeBuffer--a buffer and it's functional purpose
LengthBytes--a length (so likely unsigned and if I see differently I should pause) in bytes (not items). You would be amazed at the number of bugs this circumvents.
That's a huge amount of information conveyed in 30 bytes. But that's almost half of 80 columns. Add in something like 8 column indents and you can't even name things well.
And, that still probably doesn't give me enough information. I need to look at that to see if that is constant, and I probably need to see if I can touch that without concurrency considerations.
I read code far more often than I type it. I read code after I have forgotten about it far more often than when it is fresh.
Naming things to enable me to come back to my code 6 years later, refresh my memory, and not screw up while doing it is the single most important task in my programming.
80 columns is simply not conducive to that.
It's a bit handwavey otherwise. I'm also a long-names fan, sometimes I go overboard, but in 20 years I've not been able to logically break my line in an obvious place only once or twice.
this_is_my_very_long_variable_name = (
this_is_one_variable_that_goes_into_calculating_it +
this_is_another_variable_that_needs_considering)
The issue wasn't the fact that some expressions are longer than 80 cols (that one is longer than 120, even, its 136 characters minimum), but that they can't be clearly and appropriately written in 80 cols.It's not a trick question. I've written millions of lines of code over 20 years, and I've been renowned for using overly long names (compared to most Python coders), but I've not found that issue. Some breaks, yes, but never 'every other line', or even nearly that.
class VendorOrderShipmentResource():
...
def post(...):
...
for item in data.order_items:
...
if not shipment_qty:
abort(400, message='All order_items must have a quantity_to_ship.')
That line goes out to col 86. I realize that it's possible to break it like this (#1): abort(
400, message='All order_items must have a quantity_to_ship.')
or this (#2): abort(
400,
message='All order_items must have a quantity_to_ship.'
)
or this (#3): msg = 'All order_items must have a quantity_to_ship.'
abort(400, message=msg)
or this (#4): abort(400, message='All order_items must have a '
'quantity_to_ship')
I'm not really a fan of any of those:1. I'm not a fan of this one, in this case at least, because it "looks weird" due to the mismatch between the length of the function name, and the length of the next line. Also, I'm not a fan of "hiding" the closing parenthesis at the end of the line, rather than putting it on new line at the original indent level (like #2).
2. I'm not really a fan of this because the first argument and the function name are so much shorter than the message argument, that it looks really lopsided.
3. I'm not a fan of this because it seems stupid to create a new variable just to use it on the next line (and never again).
4. I'm not really a fan of breaking strings up this way, but sometimes I'm forced to. Breaking strings up this way tends to be really fragile when you want to modify them, and might need to reflow the entire wrap.
Personally, I'm just a fan of increasing line length to ~100. The number of things that go over 100 columns (in practice) is much smaller than the number of things that go over 80 columns.
Why is 3 stupid? That would be my preference. It is clear, simple and gets compiled away (in many cases). I alias in variables frequently, to make things more explicit. For example, I might write
iterations = 300
for _ in range(iterations):
...
which makes it less likely I'll end up repeating things or copy and pasting when new cases come up, e.g. if not shipment_qty:
abort(400, message)
elif shipment_qty < 0:
abort(400, message)
What happens in your case if you come back and edit the message and add a few characters, exceeding your longer line length?When I'm forced to break a string it is usually an indication that I need a better way of defining text than inline in expressions in code.
Just to be clear, I do use that pattern in practice, but I usually feel 'dirty' about it. Sometimes I also do things like this:
is_admin = (membership.type == 'master')
is_current_group = (membership.group == session.group)
if is_admin or is_current_group:
do_something()
but I find this easier to justify because I'm adding something more than just the breakup of a line. I'm adding semantic meaning to other developers reading the code. In the example I gave, saying "message=msg" doesn't add anything (as opposed to say "message=bad_shipment_msg", but maybe the definition of "bad_shipment_msg" pushes the string past 80 columns by itself).As for the example of reusing the message, maybe it's bad practice, but I would end up with tailored messages describing the edge case (e.g.):
if not shipment_qty:
abort(400, message='Must pass a quantity')
elif shipment_qty < 0:
abort(400, message='Quantity must be > 0')
Edit: I do the boolean definition thing more then "sometimes" because I prefer to make complex boolean operations easy to understand, and to also convey the meaning of what you want to achieve.Interesting you find aliasing in variables 'dirty'.
Would you feel dirty writing something like
def my_method(self):
contents = self.contents
... etc ...
which I use often if I'm going to use self.contents a lot in that method. It has some performance benefit in rare cases, but mostly it reduces clutter, makes lines smaller, and means I type less. But again, it is 'pointless', in one sense.From the current code open on my machine, even one character variable names!:
w, h = self.width, self.height
...
Dirty? Stupid? (I'm not being facetious, I'm genuinely interested in your aesthetic sense here).And no reason to pollute the local namespace with both self.contents and contents.
What if you do this somewhere lower for example:
contents = contents[:limit]
did you mean to NOT change self.contents? Because that's what you got. Whereas:
self.contents = self.contents[:limit]
would propagate it.
Why use them then if you have autocomplete? Since character variable names are useful as very short conventions, I'd never want to see them used generally. Someone working in this code would have to know the meaning immediately from the name, as for any variable. In my application area x,y,w,h,r (radius) and c (context - which is domain specific, but is present in about 50% of LOC in my current code-base) are all as ubiquitous as i and j (I'd probably not be comfortable with k, too deep nesting). i and j are much rarer in python, either using sequences of underscore for 'don't care'.
On the second point, there is reason, as I pointed out. It makes code shorter, clearer, and in some cases more performant (when contents is a property, for example). Don't get me wrong, I was following up on the specific point, I wasn't trying to recommend this style as good general practice (alias all your self variables at the top of a method - erm, no).
The lack of 'const'ing in python is a risk, yes. We use immutable data structures whenever possible, including immutable classes, because we try to ensure as much code is written without side effects on data. I'd want to not alias variables that have side effects applied to them, yes.
But you're right that it makes it easier for a sloppy edit to mess things up. In most cases the 'sloppy' would be adding the side effects at all, however, which either version has.
Still, in pages of such code, it helps to write
cos_t = math.cos(theta)
sin_t = math.sin(theta)
x, y = self.position.x, self.position.y
return x*cos_t - y*sin_t, x*sin_t + y*cos_t
rather than cos_theta = math.cos(theta)
sin_theta = math.sin(theta)
return self.position.x * cos_theta - self.position.y * sin_theta, self.position.x * sin_theta + self.position.y * cos_theta
particularly as the number of uses of x and y increase beyond two in a functon.Ultimately code-quality and maintainability can't be mandated by a PEP or any universal rule. But like any field, you have to understand the purpose of the rule, and weigh up its risks and benefits before you break it.
But I wouldn't ever do it for the "more performant" part, unless I have profiled the code and it indeed makes a non trivial difference (I know aliasing to a local makes a difference it itself - but I mean it should be significant when the whole program is taken into account, even if it was 1000x faster to access the aliased local, it wouldn't make sense to optimize a part of the program with it, if said part is only responsible for 1% of the total running time).
For the general case, I suggested i, j, k only because they are well known conventions, so their meaning will be instantly understood (and of course, if one can avoid such indexes altogether, e.g. with a "for item in" loop, one should).
I don't find aliasing by itself to be dirty. It is just in this example, it would not be adding semantic meaning (as in the "named boolean expressions" example) and it would be used in only a single place (immediately on the next line) so I find it to be the least justified use of aliasing.
I like introducing named variables for readability a lot. But this doesn't help readability. It just adds complexity for the sake of fitting into 80 characters.
But 'for the sake of' implies that it has no other purpose. But the purpose is readability. It will help readability for the vast population of programmers who have 80 col editors or shells, and who code according to PEP8, won't it? Isn't that the point? Of course you can say 'It would be more readable if you guessed my window width preference correctly'. But what should I guess? If I look at your code and see a 90 col line somewhere, how do I know you won't drop a 100 col line in and hurt the readability again? Do I have to read your README to find how big to make my windows? Or do I just take the readability hit for the sake of your preference?
Isn’t that the premise that is being challenged here, though? There’s plenty of variety in which editors and shells experienced programmers prefer to use, but do any of those tools really still have a hard 80 column limit in 2015?
I would guess most developers now use screens that can show at least two (and often three or even four) source files side by side and still cope with longer lines than that, and I would guess most could switch their editor or shell to cope with those wider lines in a fraction of a second with a single keyboard shortcut.
I can see cases where line length could still be a limiting factor, but I’m not sure giving up general readability improvements from allowing somewhat longer lines so it’s easier to do a three-way merge on a laptop with a smaller screen is a good trade.
If there's a way to change window size and position from 3 columns of 80 + a project browser into 120 col with a single keypress, then I'd like to know. It isn't a major problem, but it is an unnecessary problem.
Nobody is forced to use long lines either, and if you want to lay out your environment to support 120 cols and you get my 80 col source code it will work without even a keypress or a second thought. You won't even notice it.
In case it’s of any interest:
I use Sublime Text as my main editor. One of its nice features is that you can switch how the window is divided up with a single keyboard shortcut or menu command (View -> Layout). You can also easily define custom layouts in addition to the standard 2/3/4 columns, 2/3 rows, and 2x2 grid. I usually run with the editor maximised on my largest screen and 2–4 different panes open. The one feature it’s missing in this area that I’d really like to have is similarly clean support for multiple windows and therefore multiple screens. I’m not an Emacs expert, but colleagues who use it regularly do seem to have similar features available.
I also use a tool called WinSplit Revolution, which provides a somewhat similar feature for general Windows applications, letting you define some preset regions of your screen and then snap the current window to any of them with a quick keyboard shortcut. It can also shift windows between multiple monitors if you have them, again with a single quick shortcut. I can therefore instantly resize windows for things like shells, history/diff tools, and so on if I need them to be a bit wider for whatever files I’m currently working with.
Clearly you work in a different environment with different standards. I think more or less formal coding standards are important for any dev team. But they can be very different in different teams. Don't assume what you have grown used to and comfortable with is some universal truth that must fit everyone for all times.
> But what should I guess? If I look at your code and see a 90 col line somewhere, how do I know you won't drop a 100 col line in and hurt the readability again?
If we work on the same team, that is a discussion we'd need to have. Until then, let's continue being happy in our respective circumstances, OK?
2. By "Readability" I mostly mean "cognitive load" for the human reader of the code. What to fit in a line is mostly decided by what's an easily digestible chunk. If you can fit "one thought" into one line, that's ideal. Line length is a factor, but just one of many, not one that overrides every other.
So, come to think of it, I actually would break out a variable solely for line length reasons. Absolutely not for the sake of 1 character, but probably for 20.
message = "Shipment seems fine"
if not_shipment_qty:
message = "Shipment is not fine"
elif shipment_qty < 0:
message = "No quantity"
abort(400, message)If you wanted to do this pattern, it would look more like this:
message = None
if not_shipment_qty:
message = "Shipment is not fine"
elif shipment_qty < 0:
message = "No quantity"
if message:
abort(400, message=message)
(Note: In this case the keyword argument form of message is required, because all keyword args get packaged up into a JSON body of the response.) abort(400,
message='All order_items must have a quantity_to_ship.')
(not giving the parens an entire line)I'm not a fan of 80 characters, but I am a fan of less than 120 characters, if simply that there is a point where a line won't fit in a terminal thats in half of a "standard laptop" screen (i.e. the limit to where I could put two files next to each other)
Telling it to ignore all long lines means absurdly long lines are accepted too, but configuring it for a particular number makes it a hard limit. The best option is to pick a number that is a good trade-off.
Sure:
thisIsAnOutgoingBuffer = this_is_a_function(descriptiveKeywordArg = maxExchangeBufferLengthBytes, ...)
It's been a while, but I seem to recall that Python would require a continuation character to break anywhere prior to the comma.
outgoing_buffer = a_function(
descriptive_keyword=max_exchange_buffer_length_bytes
...)
Breaks nice and semantically. Calling functions with arguments on new lines is very pythonic, so much so that it is a lot of the examples of indentation in PEP8. For functions with more arguments, it is readable for the same reason that you might want to define variables on successive lines.Interestingly, notice that in your quick back-of-the-envelope example your variable and function names are quite redundant. I know it was throwaway and it wasn't your point: I'm not suggesting you were holding it up as good naming, but it is interesting that the toy examples being quoted have to work quite hard to make it bad. That suggests to me that a lot of the problem is manufactured, in practice.
> Interestingly, notice that in your quick back-of-the-envelope example your variable and function names are quite redundant.
Agreed.
The keyword argument is likely to specify things like "maxBufferLengthBytes" even as the variable does. The problem is that they serve two different purposes. The variable is moving around the program and needs to carry it's meaning with it, while the keyword argument serves to document the API that the function is part of. Because they are orthogonal, they are redundant.
In the end, the goal is productivity. I can live in 80 characters if I have to, but, if I'm in charge, it's 120-130.
Raymond's warning about reformatting introducing bugs is spot on though.
thisIsAnOutgoingBuffer = this_is_a_function(
descriptiveKeywordArg=maxExchangeBufferLengthBytes,
more_long_param=a_nested_function_call_with_long_params(
inner_param=55,
another_inner_param="Asdf"
)
) class Batman():
# ...
def handle_jokes(self, villains):
for villain in villains:
if villain.supports_jokes:
formats, transports = villain.query_joke_formats_and_transports()
# ...
That long line is 82 characters. Assume there's a good reason formats and transports are queried together. In practice this leads to variable names f and t. :( query = villain.query_joke_formats_and_transports()
formats, transports = query
or formats, transports = (
villain.query_joke_formats_and_transports())
or formats, transports = \
villain.query_joke_formats_and_transports()
Of which, we tend to do the first.Again, the longer version is clearer and simpler, but the shorter version is not so confounding that it is a slamdunk argument for longer lines, particularly as, for any length line, you could get this problem, so you'll have to do something like it eventually. If you're seeing this all over the place, it may be worth wrapping the API in more pythonic terms. 'Getters' taking no arguments, for example, are often a code-smell in python, though queries (as in your case) are a good exception, since it is also not great to have properties do expensive work (like querying a DB, say).
What other more pythonic terms could be used? The proposed rename to jokes is completely unrealistic.
It seems kind of sad to butcher such nice simple code for these artificial non-reasons.
That's why pilots are recommended to scan horizontally no more than about 10 degrees at a time when looking out for other aircraft: http://www.skybrary.aero/index.php/Visual_Scanning_Technique
Having lines be too short is also a concern for legibility, because even when lines are short, there's always the risk of the reader missing the next line at every line break.
The 80 character line is actually an artifact of punch cards, which supported 80 characters per card. And that's probably a remnant of some automatic loom that for some design reason, worked best with instructions fed in chunks of 80s.
The exact value of 80 characters might be because of IBM punched cards, but that value was chosen because printed media already had that limit, or a smaller limit, e.g. broadsheet newspaper columns. People have been writing that way for millennia.
Two other points:
(1) 70 or 90 might have been acceptable alternatives, but 80 was chosen. It's approximate. Fewer limits the amount of information per line, and more limits the font size you need to scan comfortably. A wider screen allow you to open up several windows. Having one big window isn't such a good idea.
(2) I have never been bothered by this limit, except when others break it and I have to read their code. Occasionally, sticking to it requires some thought regarding program layout. If you find yourself exceeding 80 columns often, you're probably doing something wrong. You might be nesting your code too deeply when you could call a function which handles some of your conditional logic, or your function names are too long, which suggests the underlying functions are doing too much and should be split up.
Except that you are wrong.
One of the links in the comments here has research that shows that reading speed increases significantly to at least 90 characters and may increase further than that.
http://psychology.wichita.edu/surl/usabilitynews/72/LineLeng...
I had to move my head with every line I read. (The PDF is somewhat easier on the eyes.) Not a single reference to aviation, the context where most of the related research has been done, and where research matters most, as people's lives depend upon it.
The article was specifically about reading news on-line, and was a very small study. Reading code is somewhat different to reading prose in natural language such as news. To understand a portion of code at all, you need to take in every token. With very long lines, this means having to move your eyes back and forward. Laying it out vertically makes scanning a lot easier.
Yeah, that was nice for 80 line terminals in 1986.
This is 2015. We have 27" screens. Two of them for most of us.
And on our lowly laptops, we have hi-dpi fonts (which makes smaller fonts sizes read great) and widescreens.
The number of programmers I see manically toggling through their code in one window is crazy. I think this is part of the legacy of some high-profile IDEs that gave you one window at a time.
What proportion of your code lines are > 80 cols? Multiply that by the amount of extra width to get the wasted screen estate. I'll take that and put more code/docs/tests/output on my screen at the same time and have to toggle less.
The idea that this is about teletype is very amusing, but rather naive.
Even on a widescreen laptop you can have 2 > 100 lines windows side by side. And for multi-monitor setups you can go even wilder.
>The number of programmers I see manically toggling through their code in one window is crazy. I think this is part of the legacy of some high-profile IDEs that gave you one window at a time.
If you have tons of junk in front of you, you're not focusing on your code as you should be.
Is that junk?
How many of your lines require that 100 columns, in your average code?
Of course, this can just descend into a pissing contest. The point isn't who-has-the-most-windows, but that the logic of 80cols has a practical point, and programmers who continue with the default have reasons that aren't just about teletype or not moving with the times.
Did someone stole the lower part of your screen? Or do you have all views in parallel, instead of say stacked vertically?
It all comes back to this question: what proportion of lines in your code use more than 80 cols? If it is a small proportion, the geometry is simple: you waste more screen by having longer lines.
It's obviously always gonna be a small proportion. Nobody write > 80 lines all the time (or should).
But just because you accept > 80 lines doesn't mean you have to have the editor to > 80 width. You can just let it either wrap them or cut some lines off and scroll when you want to check them (what I do).
I don't buy that somehow this "80 characters per page" is some magic number that holds true from the teletype days to today, and it should be respected regardless of font size, monitor width, etc.
I don't think anyone would suggest it is magic. It is a convention that is somewhere near to being good, and is so widely used that it is heavily tool supported, so you should have a good reason not to use it.
Actually, 80 is a bit of a misnomer, PEP8 has it at 79, git convention is 72 (50 for first line, to allow git log to display sha-summary inline and fit into 80 cols), etc.
> You can just let it either wrap them or cut some lines off and scroll when you want to check them (what I do).
Ah, I see. In which case I take back many of my objections. I see what you're saying now. Thanks.
Plus, with most editors that can jump in / out of functions, it's not like I'm losing my place. In fact, my editors all save my place in files on exit.
And you know you can put different parts of the same file in columns too? I often need to write code that is coupled to another part of the same file.
Additionally, I have two screens, and since I mainly use a tiling window manager, 80 columns works... okay. But 80 columns isn't usually where my splits end.
Interesting point with the same file in columns. It's rather that I do that. More often I'll be looking at the same file across two branches. I am kind of surprised that we don't see more plugins doing 80 column lines, in sequence, to fill the screen however. More lines on the screen seems to be the craving that vertical monitors + high-res monitors aim to satiate.
Recently code-editors have started to learn how. Don't wrap your lines at all; let the editor do that, in realtime, and you can have your windows at whatever width you want.
You're assuming you have just one window open, aren't you? There's less wasted space with 80 cols if you have multiple columns (of the same file, or different files). The math is elsewhere in this thread.
As far as I can tell, that reason is the IBM 80 column punch card standard launched in 1928:
https://en.wikipedia.org/wiki/Punched_card#IBM_80-column_pun...
Settings > Editor > General:
* check "Use soft wraps in editor"
* check "Use original line's indent for wrapped parts"
* enter 4 for "Additional shift"
* if you open huge files with wide lines, check "Show soft wraps for current line only" to prevent slowness
I like to joke God made The VT52 with 80 columns because that's how terminals should be.
Another I use is that "80 columns should be enough for anyone".
Now, seriously, code that's too wide (or too long) is a code smell. You probably should name your variables better, have fewer them in scope or just refactor the code to be more readable. Remember: code is executed by very cheaply and read by very expensive humans. Optimize for the latter.