-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
46 changed files
with
907 additions
and
976 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,353 +1,22 @@ | ||
# codemetrica | ||
# CodeMetrica | ||
|
||
########################EXhibit B ######### | ||
CodeMetrica is a work-in-progress framework designed to perform static analysis for multiple programming languages. It focuses on calculating various source code metrics and detecting code smells, providing a generalized approach to analyzing and improving code quality across different languages. | ||
|
||
To design an elegant and scalable solution for extracting different metrics (e.g., Lines of Code, comments) and detecting code smells (e.g., god class, complex conditionals), with varying implementations across programming languages, you can use the Strategy Pattern combined with Factory Pattern for each language. Here's how: | ||
## Features | ||
|
||
Design Steps: | ||
Define Common Interfaces for Metrics and Code Smells: Create abstract interfaces or base classes for the metrics and code smells you want to calculate/detect. Each programming language will implement these interfaces according to its specifics. | ||
* Multi-language support: CodeMetrica is built to support multiple programming languages, with specific support for Java and Python currently in place. | ||
* Generic code smell detection: Detect common code smells like Long Method, God Class, etc., with language-specific implementations where necessary. | ||
* Code metrics calculation: Easily compute various code metrics such as method length, cyclomatic complexity, and more. | ||
* Extensible design: Easily add support for new languages and custom code metrics. | ||
|
||
Create Separate Implementations for Each Programming Language: Implement the metrics and code smells for each programming language in its own class, adhering to the interface. This allows flexibility when adding new languages or code smells. | ||
|
||
Factory or Registry Pattern for Language-Specific Metrics/Smells: Use a factory pattern or registry to instantiate the correct metric/smell detection class based on the programming language. This keeps the code modular and extensible. | ||
## How It Works | ||
|
||
Composite Pattern for Combining Metrics and Smells: Since you need multiple metrics and code smells, consider using the Composite Pattern to group multiple checks (metrics + smells) together. | ||
* Parsing: Use ANTLR-generated parsers to extract abstract syntax trees (AST) for various languages. | ||
* Metrics & Smells: Calculate metrics and detect code smells using the parsed AST, with specific logic per language when required. | ||
|
||
Example Implementation: | ||
1. Define Common Interfaces | ||
First, define common interfaces or abstract classes for metrics and code smells that will be implemented by each language-specific class. | ||
## Status | ||
|
||
javascript | ||
Copy code | ||
// Abstract interface for calculating metrics | ||
class Metrics { | ||
calculateLOC() { | ||
throw new Error('Method calculateLOC() must be implemented'); | ||
} | ||
The project is still under development, with plans to expand language support and refine smell detection capabilities. | ||
|
||
calculateComments() { | ||
throw new Error('Method calculateComments() must be implemented'); | ||
} | ||
} | ||
|
||
// Abstract interface for detecting code smells | ||
class CodeSmells { | ||
detectGodClass() { | ||
throw new Error('Method detectGodClass() must be implemented'); | ||
} | ||
|
||
detectComplexConditional() { | ||
throw new Error('Method detectComplexConditional() must be implemented'); | ||
} | ||
} | ||
2. Language-Specific Implementations | ||
Each programming language (e.g., Python, JavaScript) will have its own implementation of the metrics and code smells interface. | ||
|
||
javascript | ||
Copy code | ||
// Python-specific implementation for metrics | ||
class PythonMetrics extends Metrics { | ||
constructor(sourceCode) { | ||
super(); | ||
this.sourceCode = sourceCode; | ||
} | ||
|
||
calculateLOC() { | ||
// Count lines of code excluding comments | ||
return this.sourceCode.split('\n').filter(line => line.trim() !== '').length; | ||
} | ||
|
||
calculateComments() { | ||
// Count the number of comment lines in Python (starting with #) | ||
return this.sourceCode.split('\n').filter(line => line.trim().startsWith('#')).length; | ||
} | ||
} | ||
|
||
// Python-specific implementation for code smells | ||
class PythonCodeSmells extends CodeSmells { | ||
constructor(ast) { | ||
super(); | ||
this.ast = ast; // Assume this is the AST from an ANTLR parser or similar | ||
} | ||
|
||
detectGodClass() { | ||
// Logic to detect god class in Python | ||
// Based on class size, number of methods, etc. | ||
} | ||
|
||
detectComplexConditional() { | ||
// Logic to detect complex conditionals in Python | ||
// Look for long chains of if-else statements | ||
} | ||
} | ||
Similarly, you can create JavaScript or other language implementations. | ||
|
||
javascript | ||
Copy code | ||
// JavaScript-specific implementation for metrics | ||
class JavaScriptMetrics extends Metrics { | ||
constructor(sourceCode) { | ||
super(); | ||
this.sourceCode = sourceCode; | ||
} | ||
|
||
calculateLOC() { | ||
// Count lines of code in JavaScript | ||
} | ||
|
||
calculateComments() { | ||
// Count comments in JavaScript (// or /* */) | ||
} | ||
} | ||
|
||
// JavaScript-specific implementation for code smells | ||
class JavaScriptCodeSmells extends CodeSmells { | ||
constructor(ast) { | ||
super(); | ||
this.ast = ast; | ||
} | ||
|
||
detectGodClass() { | ||
// Logic for detecting god class in JavaScript | ||
} | ||
|
||
detectComplexConditional() { | ||
// Logic for detecting complex conditionals in JavaScript | ||
} | ||
} | ||
3. Factory Pattern to Choose Implementation | ||
Now, use the Factory Pattern to instantiate the appropriate class based on the programming language. | ||
|
||
javascript | ||
Copy code | ||
class MetricsFactory { | ||
static getMetrics(language, sourceCode) { | ||
if (language === 'python') { | ||
return new PythonMetrics(sourceCode); | ||
} else if (language === 'javascript') { | ||
return new JavaScriptMetrics(sourceCode); | ||
} | ||
throw new Error(`Metrics for language ${language} are not supported.`); | ||
} | ||
} | ||
|
||
class CodeSmellsFactory { | ||
static getCodeSmells(language, ast) { | ||
if (language === 'python') { | ||
return new PythonCodeSmells(ast); | ||
} else if (language === 'javascript') { | ||
return new JavaScriptCodeSmells(ast); | ||
} | ||
throw new Error(`Code smells for language ${language} are not supported.`); | ||
} | ||
} | ||
4. Usage Example | ||
You can now use the factories to calculate metrics and detect code smells based on the source code and language. | ||
|
||
javascript | ||
Copy code | ||
const sourceCode = `# Python sample code\nclass MyClass:\n pass`; | ||
const language = 'python'; | ||
|
||
// Get Python-specific metrics | ||
const metrics = MetricsFactory.getMetrics(language, sourceCode); | ||
console.log('Lines of Code:', metrics.calculateLOC()); | ||
console.log('Number of Comments:', metrics.calculateComments()); | ||
|
||
// Assume we have the AST (Abstract Syntax Tree) from parsing the code | ||
const ast = {}; // Placeholder for actual AST | ||
|
||
// Get Python-specific code smells | ||
const smells = CodeSmellsFactory.getCodeSmells(language, ast); | ||
console.log('God Class Detected:', smells.detectGodClass()); | ||
console.log('Complex Conditional Detected:', smells.detectComplexConditional()); | ||
Extending to Other Languages | ||
To support another language, such as Java, you simply create new classes JavaMetrics and JavaCodeSmells, then add them to the factories. | ||
|
||
Benefits of This Design: | ||
Modularity: Each language’s specific logic for calculating metrics and detecting code smells is encapsulated in separate classes, which makes the code easy to maintain and extend. | ||
|
||
Flexibility: You can easily add new programming languages by implementing their own Metrics and CodeSmells classes without affecting the rest of the system. | ||
|
||
Separation of Concerns: Each class is responsible for a single aspect of the problem (e.g., detecting smells, calculating metrics), ensuring the code follows SOLID principles. | ||
|
||
Factory Pattern: The factory pattern provides an elegant way to choose the correct implementation at runtime, based on the input programming language. | ||
|
||
This design allows for flexibility and scalability while keeping the code clean and easy to maintain. | ||
|
||
|
||
|
||
Exhibit CCC | ||
|
||
Yes, this File class can be improved for readability, maintainability, and separation of concerns. Here are some suggestions for improving its design: | ||
|
||
1. Avoid Nesting Visitor Classes Inside Methods | ||
Nesting visitor classes inside methods can make the class harder to maintain and reuse. A better approach would be to move the ClassVisitor and FunctionVisitor outside of the File class. This separation improves code readability and allows reuse of visitors in other contexts if needed. | ||
|
||
typescript | ||
Copy code | ||
class ClassVisitor extends PythonParserVisitor<void> { | ||
classes: Class[] = []; | ||
|
||
visitClass_def(ctx: Class_defContext) { | ||
this.classes.push(new Class(ctx)); | ||
} | ||
} | ||
|
||
class FunctionVisitor extends PythonParserVisitor<void> { | ||
functions: Function[] = []; | ||
|
||
visitFunction_def(ctx: Function_defContext) { | ||
this.functions.push(new Function(ctx)); | ||
} | ||
} | ||
2. Separate Concerns | ||
The File class is currently handling file parsing, class/function extraction, and the visitor pattern implementation. You could break these responsibilities into separate components to follow the single responsibility principle. | ||
|
||
File Parsing: A separate class or utility to handle parsing. | ||
File Class: Should focus on high-level operations (e.g., getting classes and functions). | ||
Visitors: Extract visitors into their own classes. | ||
3. Improve Naming and Typing | ||
Some improvements in naming and typing could make the code more intuitive. For example: | ||
|
||
The getClasses() method could be named extractClasses() to indicate that it’s performing an action, not simply retrieving a value. | ||
Similarly, getFunctions() could be renamed to extractFunctions(). | ||
4. Use a Parser Manager | ||
Instead of directly calling Parser.getANTLRContext(path) in the constructor, it would be cleaner to have a dedicated parser manager class to handle the parsing. This would decouple the File class from the parser logic. | ||
|
||
Example: | ||
|
||
typescript | ||
Copy code | ||
class ParserManager { | ||
static getContext(path: string): File_inputContext { | ||
return Parser.getANTLRContext(path); | ||
} | ||
} | ||
|
||
export class File extends CodeBlock<File_inputContext> { | ||
path: string; | ||
|
||
constructor(path: string) { | ||
super(ParserManager.getContext(path)); | ||
this.path = path; | ||
} | ||
|
||
get name() { | ||
return this.path.split('/').pop(); | ||
} | ||
|
||
get extension() { | ||
return this.path.includes('.') ? this.path.split('.').pop() : null; | ||
} | ||
|
||
extractClasses(): Class[] { | ||
const visitor = new ClassVisitor(); | ||
visitor.visit(this.ctx); | ||
return visitor.classes; | ||
} | ||
|
||
extractFunctions(): Function[] { | ||
const visitor = new FunctionVisitor(); | ||
visitor.visit(this.ctx); | ||
return visitor.functions; | ||
} | ||
} | ||
5. Handle Errors Gracefully | ||
If parsing fails (e.g., the file path is invalid or contains syntax errors), it’s good practice to handle this gracefully with error handling: | ||
|
||
typescript | ||
Copy code | ||
constructor(path: string) { | ||
try { | ||
const context = ParserManager.getContext(path); | ||
super(context); | ||
this.path = path; | ||
} catch (error) { | ||
throw new Error(`Failed to parse file: ${path}. Error: ${error.message}`); | ||
} | ||
} | ||
6. DRY Principle (Don’t Repeat Yourself) | ||
You have similar logic for class and function extraction. You could abstract this pattern into a more generic method that accepts a visitor: | ||
|
||
typescript | ||
Copy code | ||
private visitWith<T>(VisitorClass: new () => { visit: (ctx: File_inputContext) => void }): T[] { | ||
const visitor = new VisitorClass(); | ||
visitor.visit(this.ctx); | ||
return visitor as unknown as T[]; | ||
} | ||
|
||
extractClasses(): Class[] { | ||
return this.visitWith<Class>(ClassVisitor); | ||
} | ||
|
||
extractFunctions(): Function[] { | ||
return this.visitWith<Function>(FunctionVisitor); | ||
} | ||
7. Encapsulation and Modularity | ||
To enhance encapsulation, consider making the visitor classes private or at least moving them to their own modules/files. This will keep the class focused on its role and encapsulate complexity within appropriate modules. | ||
|
||
Final Version (Refactored): | ||
typescript | ||
Copy code | ||
// ParserManager.ts | ||
export class ParserManager { | ||
static getContext(path: string): File_inputContext { | ||
return Parser.getANTLRContext(path); | ||
} | ||
} | ||
|
||
// ClassVisitor.ts | ||
export class ClassVisitor extends PythonParserVisitor<void> { | ||
classes: Class[] = []; | ||
|
||
visitClass_def(ctx: Class_defContext) { | ||
this.classes.push(new Class(ctx)); | ||
} | ||
} | ||
|
||
// FunctionVisitor.ts | ||
export class FunctionVisitor extends PythonParserVisitor<void> { | ||
functions: Function[] = []; | ||
|
||
visitFunction_def(ctx: Function_defContext) { | ||
this.functions.push(new Function(ctx)); | ||
} | ||
} | ||
|
||
// File.ts | ||
export class File extends CodeBlock<File_inputContext> { | ||
path: string; | ||
|
||
constructor(path: string) { | ||
super(ParserManager.getContext(path)); | ||
this.path = path; | ||
} | ||
|
||
get name(): string | null { | ||
return this.path.split('/').pop(); | ||
} | ||
|
||
get extension(): string | null { | ||
return this.path.includes('.') ? this.path.split('.').pop() : null; | ||
} | ||
|
||
extractClasses(): Class[] { | ||
const visitor = new ClassVisitor(); | ||
visitor.visit(this.ctx); | ||
return visitor.classes; | ||
} | ||
|
||
extractFunctions(): Function[] { | ||
const visitor = new FunctionVisitor(); | ||
visitor.visit(this.ctx); | ||
return visitor.functions; | ||
} | ||
} | ||
Key Changes: | ||
Moved visitor classes outside to separate files. | ||
Encapsulated parsing logic inside ParserManager. | ||
Renamed getClasses and getFunctions to extractClasses and extractFunctions for better clarity. | ||
Added basic error handling in the constructor. | ||
This design improves modularity, reusability, and maintainability, keeping the File class focused on its main responsibilities. | ||
Stay tuned for further updates as the project progresses! |
Oops, something went wrong.