Skip to main content

Code Quality

info

Enable quality checks: skylos . --quality

What Counts As Good Coding Practice

Skylos treats a coding practice as enforceable only when it has a clear source of authority. That source can be a language standard, a security standard, an accepted ecosystem tool, a framework convention, or an explicit repository policy.

Taste-only preferences should not become default rules. They can still be enforced as project policy, but the recommended rollout is advisory first, then blocking once the team has burned down existing findings.

Practice areaSourceSkylos / CI coverage
Python style and lint hygienePEP 8, RuffSKY-R102, advisory ruff check .
Python type clarityPEP 484, mypy / pyrightSKY-T101, SKY-T102, SKY-R101, advisory mypy
TypeScript type safetyTypeScript strict modeSKY-R105, advisory TypeScript build
Rust idioms and correctnessRustfmt, Clippyadvisory cargo fmt --check, cargo clippy
API authorization and response contractsOWASP API Security Top 10, FastAPI conventions, CWESKY-F101, SKY-F102
Maintainability and complexityISO/IEC 5055, ISO/IEC 25010, CWE coding-standard classesSKY-Q*, SKY-C*, SKY-L*
Repository enforcementpyproject.toml, .pre-commit-config.yaml, CI workflow policySKY-R101 through SKY-R105

Why Quality Metrics Matter

Complex code isn't just hard to read—it's statistically more likely to contain bugs.

Research shows that functions with complexity above 10 have significantly higher defect rates. At 20+, the function is nearly impossible to test exhaustively.

ComplexityRisk LevelDescription
1-10Low riskEasy to understand, test, and maintain
11-20Moderate riskConsider refactoring when touching this code
21+High riskLikely contains bugs, very hard to test

Cyclomatic Complexity (SKY-Q301)

Cyclomatic complexity counts the number of independent paths through a function. Each decision point adds a path.

What Adds Complexity

ConstructComplexity Added
if / elif+1 per branch
for / while+1
try / except+1 per handler
with+1
and / or+1 per operator
Ternary x if y else z+1
List/dict/set comprehension+1

Example

def process_order(order): 
if not order.valid:
return None

if order.priority == 'high':
if order.value > 1000:
apply_discount(order)
notify_manager(order)
elif order.priority == 'medium':
queue_order(order)

for item in order.items:
if item.backordered:
handle_backorder(item)
elif item.fragile:
mark_fragile(item)

return order

How to Reduce Complexity

# Before: Complexity 8
def process_order(order):
if not order.valid:
return None
# ... 50 more lines of nested logic

# After: Complexity 3 each
def process_order(order):
if not order.valid:
return None
handle_priority(order)
process_items(order)
return order

def handle_priority(order):
# Isolated complexity
pass

def process_items(order):
# Isolated complexity
pass

Nesting Depth (SKY-Q302)

Deep nesting forces readers to hold multiple conditions in working memory simultaneously.

# Depth 5 - Very hard to follow
def bad_example():
if condition1: # 1st level
for item in items: # 2nd level
if condition2: # 3rd level
try: # 4th level
if condition3: # 5th level (RIP)
process(item)
except:
pass

Severity Thresholds

DepthSeverityAction
≤3OKNo issue
4-5MEDIUMConsider refactoring
6-8HIGHShould refactor
9+CRITICALMust refactor

Reducing Nesting

# Before: Depth 4
def process_users(users):
for user in users:
if user.active:
if user.verified:
if user.has_permission:
do_something(user)

# After: Depth 1 (using continue)
def process_users(users):
for user in users:
if not user.active:
continue
if not user.verified:
continue
if not user.has_permission:
continue
do_something(user)

Structure Rules

Function Length (SKY-Q303)

Long functions usually do too much. They're hard to test and understand.

[tool.skylos]
max_lines = 50 # Default
tip

Rule of thumb: If you can't see the whole function on one screen, it's probably too long.

Argument Count (SKY-Q304)

Functions with many parameters are hard to call correctly and often indicate a missing abstraction.

# Too many arguments
def create_user(name, email, age, address, phone, department, role, manager, start_date):
pass

# Group into objects
@dataclass
class UserInfo:
name: str
email: str
age: int

@dataclass
class Employment:
department: str
role: str
manager: str
start_date: date

def create_user(info: UserInfo, employment: Employment):
pass
[tool.skylos]
max_args = 5 # Default

Logic Rules

Mutable Default Arguments (SKY-L001)

One of Python's most common gotchas:

# Bug: list is shared across all calls
def append_to(item, target=[]):
target.append(item)
return target

>>> append_to(1)
[1]
>>> append_to(2)
[1, 2] # Unexpected! Same list.
# Fixed
def append_to(item, target=None):
if target is None:
target = []
target.append(item)
return target

Bare Except (SKY-L002)

Catching everything swallows KeyboardInterrupt and SystemExit:

# Too broad
try:
risky()
except: # Catches Ctrl+C, sys.exit(), everything
pass

# Specific
try:
risky()
except Exception: # Excludes BaseException subclasses
handle_error()

Identity Comparisons (SKY-L003)

Use is for singletons:

# Wrong
if value == None:
pass

# Correct
if value is None:
pass

Standards-Backed Practice Rules

These checks are intended to catch bad coding practices that create review, security, or maintenance risk.

RuleWhat it catchesWhy it matters
SKY-T101Public functions in typed modules with untyped parametersPublic API contracts are harder to review and type-check
SKY-T102Public functions in typed modules without return annotationsCallers lose the API contract and regressions hide until runtime
SKY-F101FastAPI routes without response_model, response_class, or return annotationResponses become undocumented and harder to validate
SKY-F102Mutating routes without an obvious auth, security, or dependency guardAPI write paths need an explicit authorization boundary
SKY-R101Python repos without mypy or pyright policyType checking should be visible as repo policy
SKY-R102Python repos without Ruff policyLint policy should be explicit and repeatable
SKY-R103Repos without [tool.skylos.gate]Gate thresholds should be checked into source control
SKY-R104Repos without pre-commit configLocal staged checks should be part of the workflow
SKY-R105TypeScript packages with tsconfig.json but no tsc scriptTypeScript type checking should be runnable in CI

Reading the Output

When you run Skylos with --quality, you get a table like this:

────────────────────────── Quality Issues ──────────────────────────
# Type Name Detail Location
1 Complexity process_order Complexity: 18 (max 10) orders.py:45
2 Nesting validate_input Deep nesting: depth 6 validators.py:23
3 Structure generate_report Line count: 142, 142 lines reports.py:10
4 Quality "lokal" repeated 12× (max 3) PDF_in_Akte.py:588
5 Logic create_cache Mutable default argument cache.py:5
6 Typing create_user missing return annotation users.py:12
7 Framework Security create_user mutating route has no auth guard api.py:34

What each column means:

ColumnMeaning
TypeThe category: Complexity, Nesting, Structure, Quality (duplicate literals, coupling, cohesion), Logic, Typing, Framework, Framework Security, or Repo Policy
NameThe function, class, or string literal that triggered the finding
DetailThe measured value and the threshold. For example: Complexity: 18 (max 10) means 18 branches were found but the limit is 10. repeated 12× (max 3) means a string literal appears 12 times — extract it to a named constant
Locationfile:line where the finding starts
tip

Below the table, Skylos prints a legend explaining each metric type. You can tune all thresholds in pyproject.toml under [tool.skylos].


Configuration

[tool.skylos]
complexity = 10 # McCabe complexity threshold
nesting = 3 # Max nesting depth
max_args = 5 # Max function arguments
max_lines = 50 # Max function length
duplicate_strings = 3 # Max times a string can repeat before flagging

# Ignore specific rules
ignore = ["SKY-L003"] # Allow == None comparisons

Per-Language Overrides

TypeScript often has higher complexity in UI code:

[tool.skylos.languages.typescript]
complexity = 15
nesting = 4

Integrating Quality Gates

Block PRs that introduce complex code:

- name: Quality Gate
run: skylos . --quality --gate

With configuration:

[tool.skylos.gate]
max_quality = 10 # Fail if >10 quality issues

Advisory rollout

For existing repos, start with advisory checks so teams see the signal without failing every pull request on legacy debt. A typical advisory bundle includes:

  • ruff check .
  • mypy skylos or your chosen mypy/pyright target
  • TypeScript compile/build scripts
  • cargo fmt --check and cargo clippy for Rust crates
  • skylos . --danger --secrets --quality --diff-base origin/main --diff origin/main

Use the hard gate only after the findings are diff-aware, baseline-aware, or low enough to enforce.


Next Steps