As title says, lets say that I have a class with many methods and 80% of those:
I need to establish also as a public (1:1 functionality)
They use each other
What approach is the best in terms of extending and testing in the future? Of course below examples are trivial, but lets assume that I got class with 20-30 methods inside. If it help, the project is written in ReactJS.
Approach 1:
This approach is the fastest for me.
No code repetition
I am utilizing functions inside class, also sharing outside
Some functions depends on other
class Approach1 {
names = [];
/* Establish methods for users also */
publicApi = {
check: this.check,
setValue: this.setValue
};
/**
* Public method 1
* #public
* #param {string} name
* #returns {boolean}
*/
check = name => name === "foo";
/**
* Public method 2 using internally method 1
* #public
* #param {string} name
* #returns {boolean}
*/
setValue = name => {
if (this.check(name)) names.push(name);
};
}
Approach 2:
I don't think this approach is good.
Code repetition
I am utilizing functions inside class, also sharing outside
Functions not depending on each other
class Approach2 {
names = [];
/* Establish methods for users also */
publicApi = {
check: this.check,
setValue: this.setValue
};
/**
* Public method 1
* #public
* #param {string} name
* #returns {boolean}
*/
check = name => name === "foo";
/**
* Public method 2 with own implementation
* #public
* #param {string} name
* #returns {boolean}
*/
setValue = name => {
if (name === "foo") names.push(name);
};
}
Approach 3:
This approach looks better, but I see a lot of additional code.
Additional code (I need to encapsulate almost every private method)
I am separating same functions to private and public
Private method can depends on each other
Public methods always depends on private methods
class Approach3 {
names = [];
/* Establish methods for users also */
publicApi = {
check: this.check,
setValue: this.setValue
};
/**
* Private method 1
* #private
* #param {string} name
* #returns {boolean}
*/
_check = name => name === "foo";
/**
* Private method 2
* #private
* #param {string} name
*/
_setValue = name => {
if (this._check(name)) names.push(name);
};
/**
* Public method 1 (encapsulating private method)
* #public
* #param {string} name
* #returns {boolean}
*/
check = name => _check(name);
/**
* Public method 2 (encapsulating private methods 1 and 2)
* #public
* #param {string} name
*/
setValue = name => {
if (this._check(name)) this._setValue(name);
};
}
Thank you for any tips :)
Approach 1 looks totally fine. What you need to think first of all - is public contract. Whether some public function use another public inside or not - not important. As you still need to cover with tests each piece of public API.
p.s. having 20-30 public methods inside one class - is a good time to rethink the design and split class to multiple classes/modules to comply with Single Responsibility Principle:)
Related
I'm adding type information for TypeScript to an existing JavaScript library via JSDoc. I have a constructor function that might set a property based on the value(s) of the parameter(s) it's given:
/**
* Settings you can use to configure an instance of an {#link ExampleClass}
*
* #typedef {Object} ExampleOptions
* #property {true} [option] You can set this to `true` to make a property on an instance of {#link ExampleClass} exist. If you don't set it, that property won't exist
*/
/**
* A thing that does stuff
*
* #constructor
* #param {ExampleOptions} [param] Options you can use to configure the new instance
*/
function ExampleClass(param) {
if(param.option === true) {
/**
* A property that only exists based on the options given to this constructor
*
* #type {boolean}
*/
this.property = true;
}
}
I was hoping that TypeScript would externally interpret the declaration of property to be like property?: boolean;, but it looks like it gets interpreted to be non-optional, and comes up in the autocomplete in my editor without having to check for its existence ahead of time. Ideally, I'd like for it to be an optional property that you'd have to check for before you can use it, or even allow you to use it unchecked if TypeScript can somehow guarantee that you had passed {option: true} to the constructor. How can I make that work?
Pre-declare the class with a #typedef with the optional properties and set it to the constructor as a variable of type {new () => ExampleClass}. This way it's even possible to declare this as the defined class and have code completion inside the constructor function itself. Something like the below:
/**
* Settings you can use to configure an instance of an {#link ExampleClass}
*
* #typedef {Object} ExampleOptions
* #property {true} [option] You can set this to `true` to make a property on an instance of {#link ExampleClass} exist. If you don't set it, that property won't exist
*/
/**
* #typedef {object} ExampleClass
* #prop {boolean} [property] A property that only exists based on the options given to this constructor
*/
/**
* A thing that does stuff
*
* #constructor
* #param {ExampleOptions} [param] Options you can use to configure the new instance
* #this {ExampleClass}
* #type {{new () => ExampleClass}}
*/
const ExampleClass = function (param) {
if(param.option === true) {
this.property = true;
}
}
Can you not just do
interface options {
option? : boolean;
}
/**
* A thing that does stuff
*
* #constructor
* #param {options} [param] Options you can use to configure the new instance
*/
function ExampleClass(param : options) {
if(param.option === true) {
/**
* A property that only exists based on the options given to this constructor
*
* #type {boolean}
*/
this.property = true;
}
}
I have a js file with a bunch of functions for modifying a user.
All the functions are documented with jsdoc making it easy to see what arguments to use and what the function does.
However I now want to extend every function with an optional context variable. So from this
/**
* creates a new user object
* #param {string} email
* #param {string} displayName
* #param {string} password
* #returns {User|Error}
*/
function newUser(email, displayName, password)
to this
/**
* creates a new user object
* #param {string} email
* #param {string} displayName
* #param {string} password
* #param {*} ctx
* #returns {User|Error}
*/
function newUser(email, displayName, password, ctx)
but since I have a lot of functions that would use the same ctx variable I thought that I could create a wrapper, like this
function withContext(ctx) {
/**
* #namespace
* #borrows newUser as new
*/
return {
new: (...args) => newUser(...args, ctx),
};
}
and it does work, in the sense that the right function seems to be called with the right arguments, but I can't seem to get IntelliSense to pick it up in vscode which has me thinking that I probably haven't written the jsdoc correctly.
according to vscode it says that the new function just takes an any[], which it of course does, but it is not what the documentation for newUser states, so am I doing something wrong with my documentation or is it simply not possible?
Seems like it is possible
/**
* #typedef {Object} UserService
* #property {newUser} newUser
*/
/**
* #returns {UserService}
*/
function withContext(ctx) {
return {
newUser: (...args) => newUser(...args, ctx),
};
}
that will "fix" IntelliSense and the documentation follows the function newUser that is previously defined
My class has methods with same arguments description.
/**
* #constructor
*/
var Person = function (){};
Person.prototype = {
/**
* #param {!string} [arg] - words
*/
whisper: function(arg){
console.log(arg.toLowerCase());
},
/**
* #param {!string} [arg] - words
*/
shout: function(arg){
console.log(arg.toUpperCase());
},
};
How to share method documentation within class?
Is it possible to not only share methods documentation, but mix it with inline method description? Example: each method will have different #return type.
If I define an object like this:
/**
* My Cool Object
* #constructor
*/
function MyCoolObject() {
/**
* Cool Method, private
* #param {!string} parameter
*/
function localMethod(parameter) {
// do stuff
}
// Export the method
this.exportedMethod = localMethod;
}
I'd like to know, if at all possible, how to tell JSDOC to use the annotation for localMethod in exportedMethod, or how can I annotate exportedMethod, because if I do:
// Export the method
/**
* Cool Method
* #param {!string} parameter
*/
this.exportedMethod = localMethod;
JSDOC assumes it's a field rather than a method, then only uses the description, ignoring the #param part.
I would reduce it to:
/**
* My Cool Object
* #constructor
*/
function MyCoolObject() {
/**
* Cool Method, private
* #param {!string} parameter
*/
this.exportedMethod = function (parameter) {
// do stuff
};
}
You can do var localMethod = this.exportedMethod right after if you want a local reference to the method. In the off chance that you've over-simplified your example and you need to first assign to localMethod before assigning to this.exportedMethod you could do this:
/**
* My Cool Object
* #constructor
*/
function MyCoolObject() {
function localMethod(parameter) {
// do stuff
}
/**
* Cool Method, private
* #param {!string} parameter
* #function
*/
// Export the method
this.exportedMethod = localMethod;
}
The #function declaration tells jsdoc that it is dealing with a function.
I've got a class where individual methods may be called statically but will return a new instance of class in order to chain, for example:
var builder = ns
.setState('a', 'A')
.setState('b', 'B');
Where Builder is defined as such:
/**
* #module Builder
*/
/**
* #class Builder
*/
/**
* #private
*/
function Builder() {
this.state = {
query: {}
};
}
Builder.prototype = {
/**
* #param {string} k - The key
* #param {object} v - The value
* #return {Builder}
*/
setState: function(k, v) {
var that = (this instanceof Builder) ? this : new Builder();
that[k] = v;
return that;
}
// Other properties and methods…
}
The Builder constructor is never supposed to be called explicitly by user code and thus I'd like it not to show up in the docs. However, all of the combinations I've tried with JSDoc tags (e.g. #private, #constructs, etc.) can't seem to suppress it from the built docs.
From version 3.5.0 of jsDoc, you can use tag #hideconstructor on the class, telling jsDoc not to include the constructor into documentation.
/**
* #class Builder
*
* #hideconstructor
*/
function Builder() {
// implementation
}
You should be able to use the #ignore directive to achieve this. From the docs:
The #ignore tag indicates that a symbol in your code should never appear in the documentation. This tag takes precedence over all others.
http://usejsdoc.org/tags-ignore.html