-
-
Notifications
You must be signed in to change notification settings - Fork 389
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Rule proposal: try-maximum-statements
#651
Comments
Should this pass? 🤔 try {
return doSomething() || doSomethingElse();
} catch (err) {
// …
} This may pass. I think we can limit this rule to count the number of lines, not the number of instructions. If we don't want this to pass we may rename the rule to |
try-maximum-lines
I don't like this but I think it can help. I usually stick a lot of code inside try {
const a = maybethrows();
twistIt(a);
bopIt(a);
pullIt(a);
} catch {
// handle
} The right way would require me to use let a;
try {
a = maybethrows();
} catch {
// handle
}
twistIt(a);
bopIt(a);
pullIt(a); |
Yep the purpose of that rule is to avoid having bunch of code inside a I don't see the problem with |
let a;
try {
a = maybethrows(); If const a; // Currently an error
try {
a = maybethrows(); |
I'm lukewarm on this. I fear it will be more annoying than useful. For example, here, I don't want to expose an intermediate variable outside the try scope. let foo
try {
const bar = getBar();
foo = bar.slice(1, 2);
} catch (err) {
// …
}
console.log(foo); Maybe it should instead enforce only one function call inside "try"? |
Maybe it would just need to be sensible (i.e. no more than 3/4 lines) so it's not too annoying |
That's the problem though. What is a sensible line limit? There's no universal answer. I used to have the built-in ESLint line limit rules enabled, but ended up having to turn them off as measuring things by line numbers is too limiting and annoying. |
But why? 🤔 This rule is for people caring about well structured code and then supposed to be split into small functions? Also your example could be written on one line with no readability loss let foo
try {
foo = getBar().slice(1, 2);
} catch (err) {
// …
}
console.log(foo); The whole concept of this rule is: Only do a "single" thing in your Maybe the code sample is too easy? Can you provide us with a "real" code sample that justify multiple lines? (personally I never had to use multiple lines in my Also in a perfect world it would be better not to limit the lines of codes, but the number of "instructions". But I'm not sur to be able to define a JavaScript "instruction" with my limited knowledge |
The example was obviously artificial just to show that sometimes it makes sense to use multiple lines. Here are some examples I grepped from my projects: try {
fs.statSync('/.dockerenv');
return true;
} catch (_) {
return false;
} test('non-integer count should throw exception', async t => {
try {
await beeper(1.5);
t.fail();
} catch (error) {
t.pass();
}
}); try {
const value = await Promise.all([total, element.value]);
next(reducer(value[0], value[1], index++));
} catch (error) {
reject(error);
} try {
const stats = await promisify(fs[fsStatType])(filePath);
return stats[statsMethodName]();
} catch (error) {
if (error.code === 'ENOENT') {
return false;
}
throw error;
} try {
await subprocess;
callback();
} catch (_) {
callback(new PluginError('gulp-ava', 'One or more tests failed'));
} try {
for (const handler of this._cancelHandlers) {
handler();
}
} catch (error) {
this._reject(error);
} try {
const {stdout} = await execa(shell || defaultShell, args);
return parseEnv(stdout);
} catch (error) {
if (shell) {
throw error;
} else {
return process.env;
}
} const handleKill = async input => {
try {
input = await parseInput(input);
if (input === process.pid) {
return;
}
if (input === 'node') {
const processes = await psList();
await Promise.all(processes.map(async ps => {
if (ps.name === 'node' && ps.pid !== process.pid) {
await kill(ps.pid, options);
}
}));
return;
}
await kill(input, options);
} catch (error) {
if (!exists.get(input)) {
errors.push(`Killing process ${input} failed: Process doesn't exist`);
return;
}
errors.push(`Killing process ${input} failed: ${error.message.replace(/.*\n/, '').replace(/kill: \d+: /, '').trim()}`);
}
}; |
How would it handle nested try/catch? |
try {
fs.statSync('/.dockerenv');
} catch (_) {
return false;
}
return true;
If You may use test('non-integer count should throw exception', async () => {
await expect(
async () => beeper(1.5)
).rejects.toThrow();
});
let value;
try {
value = await Promise.all([total, element.value]);
} catch (error) {
reject(error);
return;
}
next(reducer(value[0], value[1], index++));
Not sure what exact instruction you actually are trying to // I assume we don't want to catch this
const statFunc = promisify(fs[fsStatType]);
let stats;
try {
stats = await statFunc(filePath);
} catch (error) {
if (error.code === 'ENOENT') {
return false;
}
throw error;
}
return stats[statsMethodName]();
try {
await subprocess;
} catch (_) {
callback(new PluginError('gulp-ava', 'One or more tests failed'));
return;
}
callback();
for (const handler of this._cancelHandlers) {
try {
handler();
} catch (error) {
this._reject(error);
return;
}
} Or you may wrap your loop in a dedicated function
I assume you don't want to catch let stdout;
try {
stdout = (await execa(shell || defaultShell, args)).stdout;
} catch (error) {
if (shell) {
throw error;
} else {
return process.env;
}
}
return parseEnv(stdout);
I'm pretty sur this may be refactored with function that wraps to cod that is in |
The same way.. only n lines of code allowed in So basically no nested |
try {
fs.statSync('/.dockerenv');
} catch (_) {
return false;
}
return true; While this works, I still prefer early return. As I do with for (const handler of this._cancelHandlers) {
try {
handler();
} catch (error) {
this._reject(error);
return;
}
} Try-catch comes with certain overhead, so it would be slower to put it in the loop. |
@yvele Instead of lines, how about we limit it to one statement? And maybe also an option to allow a I still feel like "number of lines" is too naive. For example, the below is still one statement: try {
result = foo
.someMethodChainMethod1()
.someMethodChainMethod2()
.someMethodChainMethod3()
} catch {} |
Yep I initially suggested a limit to one instruction, but one statement is ok
Why not. I personally will not use it.
I know that catching error comes with lots of overhead (building the call stack, etc.) but I wasn't aware that simply using Do you have any documentation to share about that |
🤷♂️ Just from memory. Might not even be valid anymore. |
This is now accepted. PR welcome. |
Name: |
I like this, should be easy to implement. |
try-maximum-lines
try-maximum-statements
@yvele You suggest let foo
try {
const bar = getBar();
foo = bar.slice(1, 2);
} catch (err) {
// …
}
console.log(foo); Into: let foo
try {
foo = getBar().slice(1, 2);
} catch (err) {
// …
}
console.log(foo); But if it's let foo
try {
const bar = getBar();
foo = bar.slice(0, Math.floor(bar.length / 2));
} catch (err) {
// …
}
console.log(foo); |
The fixes I've suggested are hypothetical. If there is multiple statements in the This rule cannot be fixable. Does that make sense? |
Another possibility is that developers won't target the right statement (if there is one) and instead group statements into a single one: const doTwoThings = () => {
doSomething();
doSomethingElse();
};
try {
doTwoThings();
} I kinda like this fact except no rule can ensure that the new function has a meaningful name (and that it can have one, given that it was created to dodge a syntactic rule). This also shows that the number of statements does not mean much really. What about this code: try {
const result = await getResult();
return result; // Could be `subscriber.next(result);`, etc.
} Do I have to create an outer mutable variable and assign to it in let result;
try {
result = await getResult();
} catch (...) {...}
return result; Should I check if I much prefer the original code. I hate when there is redundant code in a Maybe this rule is useful in some projects (I imagine), but not generally (at least coming from my experience). |
try {
return await getResult();
} 🤷♂️
Yep I think so
I've seen too many developers having a large block of code in In my opinion those advantages outweigh the rare cases where "outer mutable variable" are needed. |
I also like this rule, however I think allowing an extra |
Yes maybe we can add an option not to count |
Let's use the same algorithm from |
@fisker Sounds good. Let's start with that and we can potentially iterate based on real world experience with it. |
@fisker @sindresorhus What are your opinions on allowing an extra |
Hard to tell before check real world cases. |
The rule name may be called
unicorn/try-maximum-statements
.The idea is to avoid
try
on whole code blocks and encourage the developer to target the exactlinestatement of code that is expecting to throw.Should we add a parameter to customize the maximum number of
linestatement or hardcode it to 1 line of code? 🤔Note that I'm not a native english speaker so please fix the rule/option names.
Enforce a maximum number of
linesstatements to tryApplies to
try
clauses.The maximum number of triable
linesstatements of code configurable, but defaults to 1.This rule is not fixable.
Fail
Pass
Options
maximumNumberOfStatement
Type:
number
You can set the maximum number of statements like this:
The text was updated successfully, but these errors were encountered: