Keydonix / liquid-long

The Unlicense
8 stars 1 forks source link

Follow TSLint rules and the best practices, like semicolon after an operator #77

Open asnov opened 5 years ago

asnov commented 5 years ago
MicahZoltu commented 5 years ago

Semicolon after an operator makes code more readable - so the other developer should not think whether the operator will continue on the next line or not.

When scanning code (the most common operation IMO), the reader tends to only read the left side of the line, not the right side of the line. Because of this, the primary indicators of continuations are not the semicolon, but rather the indentation level and/or context. This is why I generally do && on the following line rather than the original line:

if (foo
    && bar
    && baz) {
    doStuff()
}

it is default TSLint rule.

I'm generally not a fan of doing something, just because everyone else does it.

Semicolons avoids ambiguity

Ambiguity in what?

For people came from C# and PHP background the code looks way more readable with semicolons than without them

I am personally from a C#/Java background, and I originally wrote JS with semicolons. I picked up Kotlin at some point along the way and after writing it for a while I learned to really appreciate how much "noise" semicolons added to the code, which has lead me to adopt the style in TypeScript/JavaScript.

In JavaScript the semicolon allows the developer to signify the end of a statement. This is very useful when it comes to minimising code. Without using semicolons you rely on JavaScript to figure it out for you. Often it will do a good job but not in 100% of cases. This is why it is always best to use semicolons.

Adding a semicolon allows the minifier to remove a newline, removing a semicolon requires the minifier to save the semicolon and remove a newline. The net result is that minification is not impacted by semicolon choice because in either case, you need a single terminating character between statements. Whether it is a semicolon or a newline doesn't matter for the sake of minification.

People using semicolons don’t need to think if it’s ok they’re using it or not, people who don’t do.

There is a single case where a semicolon is required, and it is when you start the following line with a parenthesis. If you follow a rule of "never start a line with a parenthesis, or if you do always prefix it with a semicolon" you can avoid any possible ambiguity. That being said, TypeScript's type checker will never realistically let you do that because it will tell you that you are trying to call something that isn't a function. So even though JavaScript can run into bugs due to forgetting this rule, TypeScript can't

That being said, there is something to be said for malicious code injection in a semi-colon free code base. This is probably the strongest argument for it, but I would argue that a good static analyzer or linter can very easily identify this case (starting a line with parenthesis) and warn on it. There are very few (none?) cases where you want to put an opening parenthesis on a newline as a continuation, and I would accept a linter rule that warned anytime that happened.

Better explicit than implicit

In general, I do think explicit is better than implicit. In this case however, I am of the belief that the newline is explicit. It does have dual purpose (sometimes it can result in a continuation, sometimes it is a statement terminator) but I personally find the code to be notably less noisy, especially since I do fairly few continuations.

asnov commented 5 years ago

I numbered the points as I think it could help to avoid full quotation.

When scanning code (the most common operation IMO) 1) I would agree that scanning code is the most common operation but there are others which can not be discounted, right? So this cannot be an argument.

the reader tends to only read the left side of the line, not the right side of the line. 2) Also the reader tends to but she is not ignoring the right side at all, right?

This is why I generally do && on the following line rather than the original line: 3) I do the same.

I'm generally not a fan of doing something, just because everyone else does it. 4) And I even more so. :) But over time, I noticed that when I'm not doing like everyone, then very often I step on a rake, on which nobody stepped before. Therefore, sometimes I'm doing like everyone, if it suits me. It is a question of priority: quickly walk or catch all the rakes and learn.

5) And another reason why sometimes I do something, just because everyone does it (if it does not contradict my beliefs) is because I prefer to have the freedom to change the tools and IDE whenever I want easily. Sometimes I use JetBrains IDE, sometimes VSCode, sometimes Brackets on different computers in different environment. I am not saying about linters and a bunch of CI/CD tools. And it is kind of unnecessary troubles to change their default settings every time I want to make a switching and to keep them consistent.

Ambiguity in what? 6) Whether statement terminated or continue on a new line. The absence of ending semicolons allows to use not obvious constructions which would not be possible otherwise. For example:

i
++
j
break
outerLoop
return
expression

And if something wrong is possible, sometimes it will gives consequences. Like if the gun is loaded, sooner or later it will fire.

removing a semicolon requires the minifier to save the semicolon and remove a newline 7) I missed the point.

The net result is that minification is not impacted by semicolon choice because in either case, you need a single terminating character between statements.
8) minifier doesn't use a newline as a statements delimiter. It produces one line code. The idea here is when you are not using semicolons so you're relying on minifier to put them so this could lead to errors.

There is a single case where a semicolon is required, and it is when you start the following line with a parenthesis. 9) It is not exactly like this. Starting the following line with a square bracket is second case where a semicolon is required.

if you do always prefix it with a semicolon 10) Exactly! I would say nothing if we could avoid using semicolons as statement delimiters at all but we can't. We have to put them sometimes. So it looks weird and more important, if I see the program having semicolons in some parts and mostly it doesn't, I have to spend some ticks of processor power of my brain to understand why this queerness happens. This is a problem. In routine things, I prefer the boring monotony.

MicahZoltu commented 5 years ago

Re (7) and (8), take the two following unminified code blocks:

const foo = 5;
const bar = 7
const foo = 5
const bar = 7

They will minify to:

const foo=5;const bar=7
const foo=5
const bar=7

Notice that they are both the same number of characters once minified, the only real difference is that the second one is a bit easier to read.

Separately, I'm mildly against the use of minifiers and bundlers. gzipping and HTTP/2 does almost as good of a job/better job in most cases and minification/bundling make production debugging significantly harder, and IMO are usually not worth the effort.

(10) is, IMO the most compelling argument for a project with few developers (for a project with many developers, your arguments of making your code look more like other people's code have increased value). However, in my experience with writing semicolon-free code, I think I have only needed to add a semicolon once, or maybe twice. While I will agree that semi-colon free code in TypeScript is not as nice as Kotlin (due to these situations), I don't find the cost to outweigh the benefit of noise reduction.

Interestingly, most people that write with semicolons end up leaving them off in a number of places, such as terminating a function definition. This isn't part of my argument, just an interesting inconsistency in people writing with semicolons not being particularly thorough about it.

asnov commented 5 years ago

Notice that they are both the same number of characters once minified,

I believe that there is no second option. Minifier will add semicolons and the result will look like this

const foo=5;const bar=7

regardless of whether you put a semicolon or not. At least minifiers that I used before was behaving like this but of course I didn't try all of them.

the only real difference is that the second one is a bit easier to read.

Minifier doesn't have a goal to receive an easy to read code. I would say the goal is opposite. So this could not be an argument for or against semicolumns. And I would agree with that: If you need the easiest to read code, do not use minifier.

gzipping and HTTP/2 does almost as good of a job/better job in most cases

I have nothing against gzipping and http/2 is good thing to have but it will not save us in every case. We will still need a bundler if we need: 1) to have full control of the client loading process (browser independent bundle); 2) support mobile users with weird browsers and low bandwidth; 3) easier to support in case of Subresource Integrity (SRI); 4) sign the code and give one single hash to the user to verify the bundle instead of verifying every file; The latest one could be in our requirements. Please, tell me if this is the case.

and minification/bundling make production debugging significantly harder

I can't see the difference if developer provides *.map files

(for a project with many developers, your arguments of making your code look more like other people's code have increased value)

Taking in mind that in this case our code expected to be revised by unlimited number of developers, do you need other arguments? :)))