diff options
Diffstat (limited to 'topics/programming')
-rw-r--r-- | topics/programming/better-logging.gmi | 29 | ||||
-rw-r--r-- | topics/programming/code-antipatterns.gmi | 93 | ||||
-rw-r--r-- | topics/programming/coding-guidelines.gmi | 16 | ||||
-rw-r--r-- | topics/programming/maybe-monad.gmi | 61 | ||||
-rw-r--r-- | topics/programming/use-exceptions-to-indicate-errors.gmi | 16 |
5 files changed, 215 insertions, 0 deletions
diff --git a/topics/programming/better-logging.gmi b/topics/programming/better-logging.gmi new file mode 100644 index 0000000..dca8c0d --- /dev/null +++ b/topics/programming/better-logging.gmi @@ -0,0 +1,29 @@ +# Improving Logging in GN2 + +## What Are We Trying To Solve? + +We prioritise maintaining user functionality over speed in GN [with time this speed will be improved]. As such we should be pay more attention at not breaking any currently working GN2 functionality. And when/if we do, trouble-shooting should be easy. On this front, one way is to stream-line logging in both GN2/GN3 and make it more script friendly - only report when something fails, not to instrument variables - and in so doing make the process of monitoring easier. + +## Goals + +* Have script-friendly error/info logs. +* Remove noise from GN2. +* Separate logging into different files: error logs, info logs. Add this somewhere with Flask itself instead of re-directing STDOUT to a file. + +### Non-goals + +* Logging in GN3. +* Parsing logs to extract goals. +* Getting rid of "gn.db" global object and in so doing removing "MySqlAlchemy" [that we really shouldn't be using]. +* Adding log messages to existing functions. + +## Actual Design + +* Get rid of "utility.logger" module and replace it with Flask's or Python's in-built logging. +* Configure the logging system to automatically add the module name, line number, time-stamps etc. +* Use a better format for displaying the logs. +* Delete "with Bench..." calls. + +## Resources + +=> https://realpython.com/python-logging/ Logging in Python diff --git a/topics/programming/code-antipatterns.gmi b/topics/programming/code-antipatterns.gmi new file mode 100644 index 0000000..2544451 --- /dev/null +++ b/topics/programming/code-antipatterns.gmi @@ -0,0 +1,93 @@ +# Coding Anti-Patterns + +This document contains some anti-patterns that have either been discussed during code-reviews or noticed by some one when refactoring any of GN's code-base. Use these "ideas" as a reference, and apply reasonable judgement depending on what you are working on. + + +## [Python] Don't pass mutable types as arguments + +Consider: + +``` +from typing import List + + +_l = ['a', 'b', 'c'] + + +def add_pvalue(l: List, val: str) -> List: + l.append(val) + return l + +print(f"{_l=}\n") +print(f"{add_pvalue(_l, 'd')=}\n") +print(f"{_l=}") + +``` + +which outputs (note that _l in the global scope has changed): + +``` +_l=['a', 'b', 'c'] + +add_pvalue(_l, 'd')=['a', 'b', 'c', 'd'] + +_l=['a', 'b', 'c', 'd'] +``` + +A better fix would be: + + +``` +from typing import List + + +_l = ['a', 'b', 'c'] + + +def add_pvalue(l: List, val: str) -> List: + l = l.copy() + l.append(val) + return l + +print(f"{_l=}\n") +print(f"{add_pvalue(_l, 'd')=}\n") +print(f"{_l=}") +``` + +which now does the right thing: + +``` +_l=['a', 'b', 'c'] + +add_pvalue(_l, 'd')=['a', 'b', 'c', 'd'] + +_l=['a', 'b', 'c'] + +``` + +Best, use immutable types: + +``` +from typing import Tuple + + +_l = ['a', 'b', 'c'] + + +def add_pvalue(l: Tuple, val: str) -> Tuple: + return l + (val,) + +print(f"{_l=}\n") +print(f"{add_pvalue(tuple(_l), 'p')=}\n") +print(f"{_l=}") +``` + +which outputs: + +``` +_l=['a', 'b', 'c'] + +add_pvalue(tuple(_l), 'p')=('a', 'b', 'c', 'p') + +_l=['a', 'b', 'c'] +``` diff --git a/topics/programming/coding-guidelines.gmi b/topics/programming/coding-guidelines.gmi new file mode 100644 index 0000000..8f83ba5 --- /dev/null +++ b/topics/programming/coding-guidelines.gmi @@ -0,0 +1,16 @@ +# Coding guidelines + +We aim to adhere to the following coding guidelines. + +=> /topics/use-exceptions-to-indicate-errors Exceptions, not None return values +=> /topics/maybe-monad Maybe monad, not None values +=> /topics/better-logging Log messages +=> /topics/code-antipatterns Coding Anti-Patterns +=> /topics/lisp/debugging Debugging lisp +=> /topics/lisp/common-lisp-sly Common LISP: sly +=> /topics/lisp/define-condition Common Lisp Condition Handling +=> /topics/lisp/lisp4schemers Lisp For Schemers +=> /topics/lisp/tips-and-tricks Defining a scheme sexp comment with a reader macro +=> /topics/profiling_python_code Profiling Python code + +This document is an index of other documents describing coding guidelines. Add more here as you write/discover them. diff --git a/topics/programming/maybe-monad.gmi b/topics/programming/maybe-monad.gmi new file mode 100644 index 0000000..d6f87f2 --- /dev/null +++ b/topics/programming/maybe-monad.gmi @@ -0,0 +1,61 @@ +# Maybe monad + +None values are values that represent the absence of a value. This leads to a proliferation of conditionals and special cases in the code, and is a terrible way to represent the absence of a value. We need something better. Enter the maybe monad. + +For a detailed case against None values, read +=> https://www.lucidchart.com/techblog/2015/08/31/the-worst-mistake-of-computer-science/ + +Consider the following code snippet where we print a value if it is not None. +``` +def print_unless_none(x): + if x is not None: + print(x) + +foo = 1 +bar = None +print_unless_none(foo) +print_unless_none(bar) +``` + +Rewriting the same code using the maybe monad, we can avoid the conditional check making the code more concise and more robust against bugs. +``` +from pymonad.maybe import Just, Nothing + +foo = Just(1) +bar = Nothing +foo.bind(print) +bar.bind(print) +``` + +Monads may also be passed through a chain of function calls without any condition checking in between. If foo were Nothing, the entire sequence of operations would be skipped with no error raised. Notice how this is much cleaner than interleaving the code with if conditions checking for None intermediate values. +``` +foo = Just(1) +foo.map(lambda x: 1 + x) \ + .map(lambda x: x**2) \ + .bind(print) +``` + +Finally, let's put all this together in a practical example using sql_query_mdict from genenetwork. Consider the following code using the DictCursor. The column foo may contain NULL values, and we need to check for them. +``` +with database_connection() as conn: + with conn.cursor(MySQLdb.cursors.DictCursor) as cursor: + cursor.execute("SELECT foo FROM bar") + for row in cursor.fetchall(): + if row["foo"] is not None: + print(row["foo"]) +``` +But, with sql_query_mdict, the row object is a MonadictDict where all values are monadic. We therefore do not need any special conditional checks. +``` +with database_connection() as conn: + for row in sql_query_mdict(conn, "SELECT foo FROM bar"): + row["foo"].bind(print) +``` +As a bonus, sql_query_mdict also gets rid of cursors by returning a generator and letting us iterate over it pythonically. + +## Useful Resources + +=> https://www.miguelfarrajota.com/2021/06/monads-in-python-with-pymonad/ + +=> https://jasondelaat.github.io/pymonad_docs/explanations/whats-a-monad.html + +=> https://simon.tournier.info/posts/2021-02-03-monad.html diff --git a/topics/programming/use-exceptions-to-indicate-errors.gmi b/topics/programming/use-exceptions-to-indicate-errors.gmi new file mode 100644 index 0000000..e302dd3 --- /dev/null +++ b/topics/programming/use-exceptions-to-indicate-errors.gmi @@ -0,0 +1,16 @@ +# Use exceptions to indicate errors + +Often, we indicate that a function has encountered an error by returning a None value. Here's why this is a bad idea and why you should use exceptions instead. + +When we return None values to indicate errors, we have to take care to check the return value of every function call and propagate errors higher and higher up the function call stack until we reach a point where the error is handled. This clutters up the code, and is one reason why writing correct code in languages like C that don't have exceptions is a pain. + +With exceptions, we only have to create an exception handler (try/except block in Python) at the highest level. Any exception raised by functions below that level are automatically passed on to the except block with no additional programmer effort. + +Here's an example where we run mapping, and if there's an error, we return an error page. Else, we return the results page. Notice that we do not check the return value template_vars. +``` +try: + template_vars = run_mapping.RunMapping(start_vars, temp_uuid) + return render_template("mapping_results.html", **template_vars) +except: + return render_template("mapping_error.html") +``` |