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

Make CanJS easier to debug #3278

Closed
justinbmeyer opened this Issue May 24, 2017 · 10 comments

Comments

Projects
None yet
7 participants
@justinbmeyer
Contributor

justinbmeyer commented May 24, 2017

Status

  • - canReflect.getName and canReflect.setName
  • - can-connstruct .getName
  • - can-define .getName
  • - can-observation
  • - can-compute

tldr; Lets make CanJS easier to debug! This issue seeks a variety of solutions, including:

  • Add .log() methods to most user facing types, allowing users to see what's happening.
  • Add the ability to trace (and log) how values are derived. For example, know that a textNode gets its value from a getter that gets its value from a map's first and last property.

NOTE: The Queues for error free and traceable code issue provides the ability to log the the tasks that lead to a particular piece of code being written. It also makes CanJS easier to debug, but is kept as a separate issue because of the amount of changes needed to make it happen.

This was discussed at a contributors meeting (15:50)

The Problem

Currently, it's hard to understand how a CanJS application works. Take a small example:

var view = stache(`
  <h1>{{fullName}}</h1>
  <input value:bind="first"/> <input value:bind="last"/>`);

var ViewModel = DefineMap.extend("PersonVM",{
  get fullName() {
    return this.first + " " + this.last;
  },
  first: "string",
  last: "string"
})
var viewModel = new ViewModel();
document.body.appendChild( view(viewModel) );

By looking at the h1's textNode, you can't easily know that:

  • {{fullName}} is from the fullName compute on the ViewModel
    • the fullName compute comes from first and last on the ViewModel
      • first comes from the first <input/>'s value
      • last comes from the last <input/>'s value

In addition, you can't know what a value affects. By looking at the first <input/>'s value, you can't see that:

  • It's used by the ViewModel's first property.
  • The ViewModel's first property is used by the fullName compute.
  • The fullName compute is used by {{fullName}}.

It can also be hard to know when something changes. For example, to log the value of the fullName compute, I would need to do something like:

viewModel.on("fullName", function(ev, newValue, oldValue){
  console.log("viewModel.fullName", newValue, "from",oldValue)
})

This is even harder if the viewModel is on a component.

This issue looks to solve three problems specifically:

  • Understanding the values that affect another value
  • Understanding what values a value affects
  • Knowing when state has changed

Proposed Solution

I think the solution should have two components for each problem stated above:

  1. .logX() functions that print easily understood textual information.
  2. .logXData() functions that provide the raw data used by .log that can be easily used by custom debugging tools (like a canjs chrome developer tool)

Once this is implemented, we will have a tutorial explaining the use of these APIs.

Understanding the values that affect another value

Given the example above, I propose the following can be used to log {{fullName}}'s dependencies:

var debug = require("can-debug");

var fullNameTextNode = document.querySelector("h1").firstChild

debug.logDependencies( fullNameTextNode )

This will log:

{{fullName}} <textNode> <"Justin Meyer">
  PersonVM(12).fullName <viewModel> <fullNameFunction> <"Justin Meyer">
    PersonVM(12).first <viewModel> <"Justin">
      input.value <input> <"Justin">
    PersonVM(12).last <viewModel> <"Last">
      input.value <input> <"Last">

NOTE: <VALUE> means the VALUE is logged. For example, with<textNode> the user will see the textNode object being logged, not the text <textNode>.
Also, 12 is the CID of the viewModel.

Understanding what values a value affects

Given the example above, I propose the following can be used to log the first <input>'s dep:

var debug = require("can-debug");

var firstInput = document.querySelector("input").firstChild

debug.logDependents( firstInput )

This will log:

PersonVM(12).first <viewModel> <"Justin">
  input.value <input> <"Justin">                     <--- MEANS IT DEPENDS ON ITSELF!
  PersonVM(12).fullName <viewModel> <fullNameFunction> <"Justin Meyer">
    {{fullName}} <textNode> <"Justin Meyer">

Knowing when state has changed

For most observables, I would like to add a .log() function that logs when their value changes. For example, the following:

vm.log()

Would log the following after first and last changed:

PersonVM(12).first <"Payal">
PersonVM(12).last <"Shah">
PersonVM(12).fullName <"Payal Shah">

For Maps, you can limit the values like:

vm.log("first")

The log method would be present on anything that changes values, including:

  • Observation
  • Scope
  • Compute
  • DefineMap
  • Stache expressions
  • Route
  • can-connect instance and listStores

Steps:

@justinbmeyer justinbmeyer referenced this issue May 24, 2017

Closed

CanJS 3.NEXT Epic #3277

0 of 7 tasks complete
@chasenlehara

This comment has been minimized.

Show comment
Hide comment
@chasenlehara
Member

chasenlehara commented May 24, 2017

@matthewp

This comment has been minimized.

Show comment
Hide comment
@matthewp

matthewp May 24, 2017

Contributor

I think the ability to step through state mutations is more useful than the former. You can pretty easily trace from a Node to its source template.

Contributor

matthewp commented May 24, 2017

I think the ability to step through state mutations is more useful than the former. You can pretty easily trace from a Node to its source template.

@justinbmeyer

This comment has been minimized.

Show comment
Hide comment
@justinbmeyer

justinbmeyer Jul 31, 2017

Contributor

@matthewp my experience with new users suggests the opposite. They have a lot of difficulty understanding how {{foo}} gets is value. The small apps they build don't have a lot of interacting state.

Until we have the "two queues" system @phillipskevin and I talked about ... I'm not sure how we can identify where mutations happened.

Also, we sort of have a problem with "derived" vs "linked" values. A compute derives its values from other state. So does an event stream. However, a component binding is a "linked" value. There's an independent state value, but they are connected. I wonder if reflect could help us here too.

Contributor

justinbmeyer commented Jul 31, 2017

@matthewp my experience with new users suggests the opposite. They have a lot of difficulty understanding how {{foo}} gets is value. The small apps they build don't have a lot of interacting state.

Until we have the "two queues" system @phillipskevin and I talked about ... I'm not sure how we can identify where mutations happened.

Also, we sort of have a problem with "derived" vs "linked" values. A compute derives its values from other state. So does an event stream. However, a component binding is a "linked" value. There's an independent state value, but they are connected. I wonder if reflect could help us here too.

@justinbmeyer

This comment has been minimized.

Show comment
Hide comment
@justinbmeyer

justinbmeyer Aug 1, 2017

Contributor

Notes from bitovi devs meeting:

Common Problems to address

  • Why is something not mutating?
    • Why isn't a compute or DOM node updating as a result of some change? Cases might include:
      • A compute doesn't actually read the observable.
      • The returned result is the same, so no updates happen (sort of like sticky bindings).
    • Solutions:
      • can-debug which can tell you for a compute or DOM node all source values from which it derives its value.
  • What caused some mutation?
    • Cases:
      • The page just updated due to real-time. How can you track the source down?
    • Solutions:
      • A guide showing how to break on a DOM mutation (currently in can-view-live) and trace back its origin.
      • Implementing 2Queues so mutation tasks can be clearly identified.
  • Binding to a "leaked" scope vs a binding
    • Case: A user's magic tags are on a leaked property, not a binding.
    • Solutions:
      • can-debug should be able to show people the source of the binding data.
  • Creating loops that recurse
    • Case: Users use setters that cause infinite loops
    • Solutions:
      • Warn people about using setters. This might be overkill.
      • 2Queues might be able to track this better? The same task could be created as a result of a previous task. But how to identify "sameness"?
  • Getting routes right
    • Case: Routes can be difficult to setup and get right.
    • Solutions:
      • Provide a guide around .deparam and param: { ... } -> "url"
  • The stack is hard to follow
    • Case: The stack is hard to understand because of batching.
    • Solutions:
      • Turn off batching. This will cause unexpected problems.
      • 2Queues might not clean up the stack, but will be able to provide more information about what is happening when.
      • Provide a "source" on event objects.
  • Invalid stache syntax
    • Cases: need some cases.
  • namespace collisions
    • Case: When there's an error about a namespace collision, it's not very informative on what to do.
    • Solution: Better message
  • Memory leaks
    • Case: forgetting to undo a binding
    • Solutions:
      • Warn people about using.on entirely. This sounds like overkill.
      • Use zones to track bindings during component initialization. Make sure they are undone on element removal.

Useful Debugging Techniques and gudes

The following are debugging techniques we can look to teach people in a guide:

  • Understanding scope - Teach people what Scope is and how you can look at it to see what's going on.
  • Debugging and walking the stack - Teach people some useful places within CanJS that are worth inspecting.
  • Break on change (using a setter)
  • How to setup routing rules correctly
  • Dev Env
    • Tell people how to get syntax highlighting for stache
Contributor

justinbmeyer commented Aug 1, 2017

Notes from bitovi devs meeting:

Common Problems to address

  • Why is something not mutating?
    • Why isn't a compute or DOM node updating as a result of some change? Cases might include:
      • A compute doesn't actually read the observable.
      • The returned result is the same, so no updates happen (sort of like sticky bindings).
    • Solutions:
      • can-debug which can tell you for a compute or DOM node all source values from which it derives its value.
  • What caused some mutation?
    • Cases:
      • The page just updated due to real-time. How can you track the source down?
    • Solutions:
      • A guide showing how to break on a DOM mutation (currently in can-view-live) and trace back its origin.
      • Implementing 2Queues so mutation tasks can be clearly identified.
  • Binding to a "leaked" scope vs a binding
    • Case: A user's magic tags are on a leaked property, not a binding.
    • Solutions:
      • can-debug should be able to show people the source of the binding data.
  • Creating loops that recurse
    • Case: Users use setters that cause infinite loops
    • Solutions:
      • Warn people about using setters. This might be overkill.
      • 2Queues might be able to track this better? The same task could be created as a result of a previous task. But how to identify "sameness"?
  • Getting routes right
    • Case: Routes can be difficult to setup and get right.
    • Solutions:
      • Provide a guide around .deparam and param: { ... } -> "url"
  • The stack is hard to follow
    • Case: The stack is hard to understand because of batching.
    • Solutions:
      • Turn off batching. This will cause unexpected problems.
      • 2Queues might not clean up the stack, but will be able to provide more information about what is happening when.
      • Provide a "source" on event objects.
  • Invalid stache syntax
    • Cases: need some cases.
  • namespace collisions
    • Case: When there's an error about a namespace collision, it's not very informative on what to do.
    • Solution: Better message
  • Memory leaks
    • Case: forgetting to undo a binding
    • Solutions:
      • Warn people about using.on entirely. This sounds like overkill.
      • Use zones to track bindings during component initialization. Make sure they are undone on element removal.

Useful Debugging Techniques and gudes

The following are debugging techniques we can look to teach people in a guide:

  • Understanding scope - Teach people what Scope is and how you can look at it to see what's going on.
  • Debugging and walking the stack - Teach people some useful places within CanJS that are worth inspecting.
  • Break on change (using a setter)
  • How to setup routing rules correctly
  • Dev Env
    • Tell people how to get syntax highlighting for stache
@justinbmeyer

This comment has been minimized.

Show comment
Hide comment
@justinbmeyer

justinbmeyer Aug 15, 2017

Contributor

show mutating VM

Contributor

justinbmeyer commented Aug 15, 2017

show mutating VM

@mjstahl

This comment has been minimized.

Show comment
Hide comment
@mjstahl

mjstahl Aug 15, 2017

Member

Seaside's concept of Halo's was in an inspiration for me working on the done-inspect code, and is a high level goal.

http://seaside.st/about/examples/halos

Member

mjstahl commented Aug 15, 2017

Seaside's concept of Halo's was in an inspiration for me working on the done-inspect code, and is a high level goal.

http://seaside.st/about/examples/halos

@phillipskevin

This comment has been minimized.

Show comment
Hide comment
@phillipskevin

phillipskevin Sep 20, 2017

Collaborator

Expose viewModel on component element in development - canjs/can-debug#8

Collaborator

phillipskevin commented Sep 20, 2017

Expose viewModel on component element in development - canjs/can-debug#8

@justinbmeyer justinbmeyer changed the title from can-debug to Make CanJS easier to debug Sep 22, 2017

@phillipskevin phillipskevin added the Epic label Sep 28, 2017

@phillipskevin

This comment has been minimized.

Show comment
Hide comment
@phillipskevin

phillipskevin Sep 29, 2017

Collaborator

here are the original steps from the description:

  • - Survey DoneJS Community members and Bitovian's about common gotcha problems.
  • - Support tracing through an element to its source value.
  • - Support debugger in stache so that people can explore the scope.
  • - Write a guide or FAQ on debugging CanJS applications. Topics:
    • - Find the origin of a mutation.
Collaborator

phillipskevin commented Sep 29, 2017

here are the original steps from the description:

  • - Survey DoneJS Community members and Bitovian's about common gotcha problems.
  • - Support tracing through an element to its source value.
  • - Support debugger in stache so that people can explore the scope.
  • - Write a guide or FAQ on debugging CanJS applications. Topics:
    • - Find the origin of a mutation.

@andrejewski andrejewski removed their assignment Oct 6, 2017

@justinbmeyer

This comment has been minimized.

Show comment
Hide comment
@justinbmeyer

justinbmeyer Oct 10, 2017

Contributor

As we need convention for logging information for a wide variety of types. I'm thinking canReflect.getName() might return the following:

  • Instances of map-likes Person{}

  • Instances of array-likes People[]

  • Instances of value-likes Observation<>

  • Method Person{}.eat (This implies we decorated all methods with info on the constructor)

  • If we want to provide additional information, like a cid or current value, we can do that between {}, [], or <>

For example, say we have a can-connect-ed Person type where we know the unique id property. We might have a instance of person log like:

Person{22}

Or say we DO have a CID, then we can use it on those types like:

Observation<21>

Or maybe an observation logs its value:

Observation<"Justin Meyer">

A list might have its length:

People[5]

Other things we need to log:

  • properties
  • expressions
  • dom nodes
Contributor

justinbmeyer commented Oct 10, 2017

As we need convention for logging information for a wide variety of types. I'm thinking canReflect.getName() might return the following:

  • Instances of map-likes Person{}

  • Instances of array-likes People[]

  • Instances of value-likes Observation<>

  • Method Person{}.eat (This implies we decorated all methods with info on the constructor)

  • If we want to provide additional information, like a cid or current value, we can do that between {}, [], or <>

For example, say we have a can-connect-ed Person type where we know the unique id property. We might have a instance of person log like:

Person{22}

Or say we DO have a CID, then we can use it on those types like:

Observation<21>

Or maybe an observation logs its value:

Observation<"Justin Meyer">

A list might have its length:

People[5]

Other things we need to log:

  • properties
  • expressions
  • dom nodes
@justinbmeyer

This comment has been minimized.

Show comment
Hide comment
@justinbmeyer

justinbmeyer Jan 23, 2018

Contributor

Closing as solved in 4.0

Contributor

justinbmeyer commented Jan 23, 2018

Closing as solved in 4.0

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment