Skip to content
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

Add check on infohash key existing #5910

Merged
merged 2 commits into from
Jan 11, 2021
Merged

Conversation

drew2a
Copy link
Contributor

@drew2a drew2a commented Jan 7, 2021

This PR resolves #5909 by adding a check on the existing of infohash key.

The root cause of the issue is too general an inaccurate design of ContentTableViewController.
My proposal: to schedule a refactoring for ContentTableViewController.

@@ -132,6 +132,9 @@ def _on_selection_changed(self, selected, deselected):
class HealthCheckerMixin:
def check_torrent_health(self, data_item, forced=False):
# TODO: stop triggering multiple checks over a single infohash by e.g. selection and click signals
if 'infohash' not in data_item:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I understand it, channels have infohashes as well as regular torrents, but we probably don't want to show health information for channels in this view.

In other similar places, there is a check if data_item['type'] != REGULAR_TORRENT. I think we need to use a similar check here as well.

Copy link
Contributor Author

@drew2a drew2a Jan 7, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checking infohash is a more reliable approach if we look at the change as a "bugfix".

But your suggestion is the more in "triblertablecontrollers' style". And your approach is more correct if we look at the change as a "triblertablecontrollers fix".

I've implemented your suggestion (still not sure that this is the best version of the bugfix).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is indeed more correct to check for the entry type, not for its attributes. In this way, we catch the errors in the application logic, though at the cost of the thing crashing for the user.

@drew2a drew2a force-pushed the fix/5909 branch 3 times, most recently from 94fe4b1 to 88985ee Compare January 7, 2021 11:38
@@ -287,7 +293,7 @@ def add_menu_item(self, menu, name, item_index, callback):
def selection_can_be_added_to_channel(self):
for row in self.table_view.selectionModel().selectedRows():
data_item = row.model().data_items[row.row()]
if 'type' in data_item and data_item['type'] in (REGULAR_TORRENT, CHANNEL_TORRENT, COLLECTION_NODE):
if dict_has(data_item, 'type', REGULAR_TORRENT, CHANNEL_TORRENT, COLLECTION_NODE):
Copy link
Contributor

@kozlovsky kozlovsky Jan 7, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, I don't like this new dict_has function. In my opinion, it makes code much harder to understand.

  1. Previously the code did not require additional context to understand what is going on. Now I need to look at the dict_has source to understand the details of its logic.
  2. In the previous code it was clear to me that data_item is a dict instance (or, at least, some objects with the Mapping interface). Now the code will not break if data_item is, for example, None. So, if I need to extend this function in the future, I need to spend time to answer the question "can data_item be anything other than dict", whereas in the previous version it was obvious.
  3. The new version is not significantly shorter. If the goal is to achieve brevity it was possible to write instead:
if data_item.get('type') in (REGULAR_TORRENT, CHANNEL_TORRENT, COLLECTION_NODE):
    ...

So I propose to roll back this change and remove the dict_has function.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Python is a dynamically typed language but it is strongly typed. The expressionfoo[bar] generates KeyError exception when it is incorrectly assumed that foo should be a mapping and bar is a required item of this mapping, and it is a good thing. It is like a programmer explicitly adds some assertions to its code which will guarantee that errors will raise fast and not be propagated to different places. When we add unnecessary checks if foo or if bar in foo we essentially convert strongly typed language to a weakly typed one.

Copy link
Contributor Author

@drew2a drew2a Jan 7, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Previously the code did not require additional context to understand what is going on. Now I need to look at the dict_has source to understand the details of its logic.

Yes, and this is ok. You need to understand once what this function does, and then relax for all other occurrences.
dict_has just extract a pattern that was used in this file three times

Now the code will not break if data_item is, for example, None

Yes, and this is great. No excuses for crash on the user side.

The new version is not significantly shorter.

Yes, but shortnesses is doubtful purpose. The new version reduces the number of repetitions (of a pattern itself and of key's value, inside the pattern). Therefore it decreases the chance to make a mistake.

Copy link
Contributor

@kozlovsky kozlovsky Jan 7, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dict_has just extract a pattern that was used in this file three times

Not all patterns probably should be extracted. a + b * c is better than addmul(a, b, c)

Yes, and this is great. No excuses for crash on the user side.

If we a sure that, say, something can never be None, adding unnecessary handling for None leads to less clear code, as from now it says to the future reader: there are some obscure cases when this variable really can be None.

Yes, and this is ok. You need to understand once what this function does, and then relax for all other occurrences.

Many developers need to do it, and even a single developer does not remember all details of all the small functions that he reads in the entire codebase and needs to read it again.

Actually, this specific case is small enough to turn it into the bikeshedding, but the general pattern looks a bit like a code obfuscation to me.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with @kozlovsky . Python is not C++: Python programmers are used to some idiomatic ways to do simple stuff like checking dict attributes, etc. Python is not about inventing your own sub-language as C++ does. Python is about making the code immediately readable to anyone.

So, replacing an explicit one-liner with an implicit one-liner goes against the Zen of Python

import this 😉

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dijkstra would say that exceptions in reports can be used to show the presence of bugs, but never to show their absence :)

Copy link
Contributor Author

@drew2a drew2a Jan 7, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ichorid

So, every release must look as stable as possible, even if it means hiding defects, right?

Probably it is better to just answer "yes" on your question.

But I would say, if a release doesn't look stable, you shouldn't publish it. Taking into account, that "obscure cases" always will be presented... we need to make release stable with the margin of safety.

(in my humble view)

This comment was marked as outdated.

This comment was marked as outdated.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dijkstra would say that exceptions in reports can be used to show the presence of bugs, but never to show their absence :)

Of course. Yet exceptions is almost the only kind of feedback and support that we get from our userbase. Because, you know, we can't do telemetry.

@@ -422,3 +422,9 @@ def disconnect(signal: pyqtSignal, callback: Callable):

class CreationTraceback(Exception):
pass


def dict_has(d, key, *values_or):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still think it is better to inline code without a function, but it is more important to have this fix in the release.
How about changing the function signature to

def dict_item_is_any_of(d, key, values):

Just a suggestion, as the values_or argument name requires to look at the function signature to understand the meaning of multiple values.

@sonarcloud
Copy link

sonarcloud bot commented Jan 11, 2021

Kudos, SonarCloud Quality Gate passed!

Bug A 0 Bugs
Vulnerability A 0 Vulnerabilities
Security Hotspot A 0 Security Hotspots
Code Smell A 0 Code Smells

No Coverage information No Coverage information
No Duplication information No Duplication information

@drew2a drew2a merged commit 47dbcfd into Tribler:release-7.7 Jan 11, 2021
@drew2a drew2a deleted the fix/5909 branch January 11, 2021 11:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

4 participants